Close shader authoring usepass regressions
This commit is contained in:
@@ -1,14 +1,300 @@
|
||||
#include "ShaderRuntimeBuildUtils.h"
|
||||
|
||||
#include "../ShaderAuthoringParser.h"
|
||||
#include "ShaderFileUtils.h"
|
||||
#include "../ShaderSourceUtils.h"
|
||||
#include "ShaderAuthoringInternal.h"
|
||||
#include "ShaderFileUtils.h"
|
||||
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
|
||||
#include <filesystem>
|
||||
#include <memory>
|
||||
#include <system_error>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
Containers::String ResolveBuiltinShaderPathByAuthoringName(const Containers::String& shaderName) {
|
||||
Containers::String builtinShaderPath;
|
||||
if (TryGetBuiltinShaderPathByShaderName(shaderName, builtinShaderPath)) {
|
||||
return builtinShaderPath;
|
||||
}
|
||||
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
void AddUniqueSearchRoot(
|
||||
const fs::path& candidate,
|
||||
std::vector<fs::path>& searchRoots,
|
||||
std::unordered_set<std::string>& seenRoots) {
|
||||
if (candidate.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fs::path normalized = candidate.lexically_normal();
|
||||
const std::string key = normalized.generic_string();
|
||||
if (key.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (seenRoots.insert(key).second) {
|
||||
searchRoots.push_back(normalized);
|
||||
}
|
||||
}
|
||||
|
||||
bool TryReadDeclaredAuthoringShaderName(
|
||||
const Containers::String& shaderPath,
|
||||
Containers::String& outShaderName) {
|
||||
outShaderName.Clear();
|
||||
|
||||
const Containers::Array<Core::uint8> data = ReadShaderFileData(shaderPath);
|
||||
if (data.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::string sourceText = ToStdStringFromBytes(data);
|
||||
std::vector<std::string> lines;
|
||||
Internal::SplitShaderAuthoringLines(sourceText, lines);
|
||||
if (lines.empty() || !Internal::StartsWithKeyword(lines.front(), "Shader")) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<std::string> tokens;
|
||||
if (!Internal::TryTokenizeQuotedArguments(lines.front(), tokens) || tokens.size() < 2u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outShaderName = tokens[1].c_str();
|
||||
return !outShaderName.Empty();
|
||||
}
|
||||
|
||||
bool TryResolveProjectShaderPathByAuthoringName(
|
||||
const Containers::String& currentShaderPath,
|
||||
const Containers::String& shaderName,
|
||||
Containers::String& outResolvedPath) {
|
||||
outResolvedPath.Clear();
|
||||
|
||||
fs::path currentPath(currentShaderPath.CStr());
|
||||
if (!currentPath.is_absolute()) {
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (!resourceRoot.Empty()) {
|
||||
currentPath = fs::path(resourceRoot.CStr()) / currentPath;
|
||||
}
|
||||
}
|
||||
|
||||
if (currentPath.empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::vector<fs::path> searchRoots;
|
||||
std::unordered_set<std::string> seenRoots;
|
||||
|
||||
AddUniqueSearchRoot(currentPath.parent_path(), searchRoots, seenRoots);
|
||||
|
||||
for (fs::path ancestor = currentPath.parent_path();
|
||||
!ancestor.empty();
|
||||
ancestor = ancestor.parent_path()) {
|
||||
std::error_code ec;
|
||||
const fs::path assetsRoot = ancestor / "Assets";
|
||||
if (fs::exists(assetsRoot, ec) && fs::is_directory(assetsRoot, ec)) {
|
||||
AddUniqueSearchRoot(assetsRoot, searchRoots, seenRoots);
|
||||
}
|
||||
|
||||
const fs::path parent = ancestor.parent_path();
|
||||
if (parent == ancestor) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
std::vector<fs::path> matches;
|
||||
std::unordered_set<std::string> seenMatches;
|
||||
|
||||
for (const fs::path& root : searchRoots) {
|
||||
std::error_code ec;
|
||||
if (!fs::exists(root, ec) || !fs::is_directory(root, ec)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (fs::recursive_directory_iterator it(root, ec), end; !ec && it != end; it.increment(ec)) {
|
||||
if (!it->is_regular_file()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fs::path candidatePath = it->path();
|
||||
if (candidatePath.extension() != ".shader") {
|
||||
continue;
|
||||
}
|
||||
|
||||
Containers::String declaredShaderName;
|
||||
if (!TryReadDeclaredAuthoringShaderName(
|
||||
Containers::String(candidatePath.generic_string().c_str()),
|
||||
declaredShaderName) ||
|
||||
declaredShaderName != shaderName) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string matchKey = candidatePath.lexically_normal().generic_string();
|
||||
if (seenMatches.insert(matchKey).second) {
|
||||
matches.push_back(candidatePath.lexically_normal());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (matches.size() != 1u) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outResolvedPath = matches.front().generic_string().c_str();
|
||||
return true;
|
||||
}
|
||||
|
||||
void ImportConcretePass(Shader& shader, const ShaderPass& sourcePass) {
|
||||
if (ShaderPass* existingPass = shader.FindPass(sourcePass.name)) {
|
||||
*existingPass = sourcePass;
|
||||
return;
|
||||
}
|
||||
|
||||
shader.AddPass(sourcePass);
|
||||
}
|
||||
|
||||
ShaderPass BuildConcretePass(
|
||||
const ShaderIR& shaderIR,
|
||||
const ShaderSubShaderIR& subShader,
|
||||
const ShaderPassIR& pass) {
|
||||
ShaderPass shaderPass = {};
|
||||
shaderPass.name = pass.name;
|
||||
shaderPass.hasFixedFunctionState = pass.hasFixedFunctionState;
|
||||
shaderPass.fixedFunctionState = pass.fixedFunctionState;
|
||||
|
||||
for (const ShaderTagIR& subShaderTag : subShader.tags) {
|
||||
shaderPass.tags.PushBack({ subShaderTag.name, subShaderTag.value });
|
||||
}
|
||||
for (const ShaderTagIR& passTag : pass.tags) {
|
||||
shaderPass.tags.PushBack({ passTag.name, passTag.value });
|
||||
}
|
||||
for (const ShaderResourceBindingDesc& resourceBinding : pass.resources) {
|
||||
shaderPass.resources.PushBack(resourceBinding);
|
||||
}
|
||||
for (const ShaderKeywordDeclaration& keywordDeclaration : pass.keywordDeclarations) {
|
||||
shaderPass.keywordDeclarations.PushBack(keywordDeclaration);
|
||||
}
|
||||
|
||||
if (pass.programSource.Empty()) {
|
||||
return shaderPass;
|
||||
}
|
||||
|
||||
Containers::String combinedSource;
|
||||
AppendAuthoringSourceBlock(combinedSource, shaderIR.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, subShader.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, pass.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, pass.programSource);
|
||||
|
||||
const Containers::String strippedCombinedSource = StripShaderAuthoringPragmas(combinedSource);
|
||||
const std::vector<ShaderKeywordSet> keywordSets =
|
||||
BuildShaderKeywordVariantSets(pass.keywordDeclarations);
|
||||
|
||||
for (const ShaderKeywordSet& keywordSet : keywordSets) {
|
||||
const Containers::String variantSource =
|
||||
BuildKeywordVariantSource(strippedCombinedSource, keywordSet);
|
||||
|
||||
ShaderStageVariant vertexVariant = {};
|
||||
vertexVariant.stage = ShaderType::Vertex;
|
||||
vertexVariant.backend = ShaderBackend::Generic;
|
||||
vertexVariant.language = ShaderLanguage::HLSL;
|
||||
vertexVariant.requiredKeywords = keywordSet;
|
||||
vertexVariant.entryPoint =
|
||||
!pass.vertexEntryPoint.Empty()
|
||||
? pass.vertexEntryPoint
|
||||
: GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Vertex);
|
||||
vertexVariant.profile = GetDefaultProfile(
|
||||
ShaderLanguage::HLSL,
|
||||
ShaderBackend::Generic,
|
||||
ShaderType::Vertex);
|
||||
vertexVariant.sourceCode = variantSource;
|
||||
shaderPass.variants.PushBack(vertexVariant);
|
||||
|
||||
ShaderStageVariant fragmentVariant = {};
|
||||
fragmentVariant.stage = ShaderType::Fragment;
|
||||
fragmentVariant.backend = ShaderBackend::Generic;
|
||||
fragmentVariant.language = ShaderLanguage::HLSL;
|
||||
fragmentVariant.requiredKeywords = keywordSet;
|
||||
fragmentVariant.entryPoint =
|
||||
!pass.fragmentEntryPoint.Empty()
|
||||
? pass.fragmentEntryPoint
|
||||
: GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Fragment);
|
||||
fragmentVariant.profile = GetDefaultProfile(
|
||||
ShaderLanguage::HLSL,
|
||||
ShaderBackend::Generic,
|
||||
ShaderType::Fragment);
|
||||
fragmentVariant.sourceCode = variantSource;
|
||||
shaderPass.variants.PushBack(fragmentVariant);
|
||||
}
|
||||
|
||||
return shaderPass;
|
||||
}
|
||||
|
||||
bool TryResolveUsePass(
|
||||
const Containers::String& currentShaderPath,
|
||||
const Containers::String& currentShaderName,
|
||||
const Containers::String& referencedShaderName,
|
||||
const Containers::String& referencedPassName,
|
||||
const std::unordered_map<std::string, ShaderPass>& localConcretePasses,
|
||||
ShaderPass& outPass,
|
||||
Containers::String& outError) {
|
||||
if (referencedShaderName == currentShaderName) {
|
||||
const auto passIt = localConcretePasses.find(ToStdString(referencedPassName));
|
||||
if (passIt == localConcretePasses.end()) {
|
||||
outError =
|
||||
Containers::String("UsePass could not resolve local pass: ") + referencedPassName;
|
||||
return false;
|
||||
}
|
||||
|
||||
outPass = passIt->second;
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String referencedShaderPath;
|
||||
if (!ResolveShaderUsePassPath(
|
||||
currentShaderPath,
|
||||
currentShaderName,
|
||||
referencedShaderName,
|
||||
referencedShaderPath)) {
|
||||
outError =
|
||||
Containers::String("UsePass could not resolve referenced shader: ") +
|
||||
referencedShaderName;
|
||||
return false;
|
||||
}
|
||||
|
||||
ShaderLoader loader;
|
||||
LoadResult referencedShaderResult = loader.Load(referencedShaderPath);
|
||||
if (!referencedShaderResult || referencedShaderResult.resource == nullptr) {
|
||||
outError =
|
||||
Containers::String("UsePass failed to load referenced shader: ") + referencedShaderName;
|
||||
return false;
|
||||
}
|
||||
|
||||
std::unique_ptr<Shader> referencedShader(static_cast<Shader*>(referencedShaderResult.resource));
|
||||
const ShaderPass* referencedPass = referencedShader->FindPass(referencedPassName);
|
||||
if (referencedPass == nullptr) {
|
||||
outError =
|
||||
Containers::String("UsePass could not find referenced pass: ") + referencedPassName;
|
||||
return false;
|
||||
}
|
||||
|
||||
outPass = *referencedPass;
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
Containers::String GetDefaultEntryPoint(ShaderLanguage language, ShaderType stage) {
|
||||
if (language == ShaderLanguage::HLSL) {
|
||||
switch (stage) {
|
||||
@@ -98,6 +384,33 @@ size_t CalculateShaderMemorySize(const Shader& shader) {
|
||||
return memorySize;
|
||||
}
|
||||
|
||||
bool ResolveShaderUsePassPath(
|
||||
const Containers::String& currentShaderPath,
|
||||
const Containers::String& currentShaderName,
|
||||
const Containers::String& targetShaderName,
|
||||
Containers::String& outResolvedPath) {
|
||||
outResolvedPath.Clear();
|
||||
|
||||
if (targetShaderName.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (targetShaderName == currentShaderName) {
|
||||
outResolvedPath = currentShaderPath;
|
||||
return true;
|
||||
}
|
||||
|
||||
outResolvedPath = ResolveBuiltinShaderPathByAuthoringName(targetShaderName);
|
||||
if (!outResolvedPath.Empty()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return TryResolveProjectShaderPathByAuthoringName(
|
||||
currentShaderPath,
|
||||
targetShaderName,
|
||||
outResolvedPath);
|
||||
}
|
||||
|
||||
LoadResult BuildShaderFromIR(
|
||||
const Containers::String& path,
|
||||
const ShaderIR& shaderIR) {
|
||||
@@ -113,76 +426,56 @@ LoadResult BuildShaderFromIR(
|
||||
shader->AddProperty(property);
|
||||
}
|
||||
|
||||
std::unordered_map<std::string, ShaderPass> localConcretePasses;
|
||||
for (const ShaderSubShaderIR& subShader : shaderIR.subShaders) {
|
||||
for (const ShaderPassIR& pass : subShader.passes) {
|
||||
ShaderPass shaderPass = {};
|
||||
shaderPass.name = pass.name;
|
||||
shaderPass.hasFixedFunctionState = pass.hasFixedFunctionState;
|
||||
shaderPass.fixedFunctionState = pass.fixedFunctionState;
|
||||
shader->AddPass(shaderPass);
|
||||
|
||||
for (const ShaderTagIR& subShaderTag : subShader.tags) {
|
||||
shader->SetPassTag(pass.name, subShaderTag.name, subShaderTag.value);
|
||||
}
|
||||
for (const ShaderTagIR& passTag : pass.tags) {
|
||||
shader->SetPassTag(pass.name, passTag.name, passTag.value);
|
||||
if (pass.isUsePass) {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (const ShaderResourceBindingDesc& resourceBinding : pass.resources) {
|
||||
shader->AddPassResourceBinding(pass.name, resourceBinding);
|
||||
}
|
||||
for (const ShaderKeywordDeclaration& keywordDeclaration : pass.keywordDeclarations) {
|
||||
shader->AddPassKeywordDeclaration(pass.name, keywordDeclaration);
|
||||
ShaderPass concretePass = BuildConcretePass(shaderIR, subShader, pass);
|
||||
const std::string passKey = ToStdString(concretePass.name);
|
||||
if (passKey.empty()) {
|
||||
return LoadResult("Shader authoring produced a pass with an empty name");
|
||||
}
|
||||
|
||||
if (!pass.programSource.Empty()) {
|
||||
Containers::String combinedSource;
|
||||
AppendAuthoringSourceBlock(combinedSource, shaderIR.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, subShader.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, pass.sharedProgramSource);
|
||||
AppendAuthoringSourceBlock(combinedSource, pass.programSource);
|
||||
const Containers::String strippedCombinedSource =
|
||||
StripShaderAuthoringPragmas(combinedSource);
|
||||
const std::vector<ShaderKeywordSet> keywordSets =
|
||||
BuildShaderKeywordVariantSets(pass.keywordDeclarations);
|
||||
if (!localConcretePasses.emplace(passKey, std::move(concretePass)).second) {
|
||||
return LoadResult(
|
||||
Containers::String("Shader authoring produced duplicate pass name: ") + pass.name);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (const ShaderKeywordSet& keywordSet : keywordSets) {
|
||||
const Containers::String variantSource =
|
||||
BuildKeywordVariantSource(strippedCombinedSource, keywordSet);
|
||||
|
||||
ShaderStageVariant vertexVariant = {};
|
||||
vertexVariant.stage = ShaderType::Vertex;
|
||||
vertexVariant.backend = ShaderBackend::Generic;
|
||||
vertexVariant.language = ShaderLanguage::HLSL;
|
||||
vertexVariant.requiredKeywords = keywordSet;
|
||||
vertexVariant.entryPoint =
|
||||
!pass.vertexEntryPoint.Empty()
|
||||
? pass.vertexEntryPoint
|
||||
: GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Vertex);
|
||||
vertexVariant.profile = GetDefaultProfile(
|
||||
ShaderLanguage::HLSL,
|
||||
ShaderBackend::Generic,
|
||||
ShaderType::Vertex);
|
||||
vertexVariant.sourceCode = variantSource;
|
||||
shader->AddPassVariant(pass.name, vertexVariant);
|
||||
|
||||
ShaderStageVariant fragmentVariant = {};
|
||||
fragmentVariant.stage = ShaderType::Fragment;
|
||||
fragmentVariant.backend = ShaderBackend::Generic;
|
||||
fragmentVariant.language = ShaderLanguage::HLSL;
|
||||
fragmentVariant.requiredKeywords = keywordSet;
|
||||
fragmentVariant.entryPoint =
|
||||
!pass.fragmentEntryPoint.Empty()
|
||||
? pass.fragmentEntryPoint
|
||||
: GetDefaultEntryPoint(ShaderLanguage::HLSL, ShaderType::Fragment);
|
||||
fragmentVariant.profile = GetDefaultProfile(
|
||||
ShaderLanguage::HLSL,
|
||||
ShaderBackend::Generic,
|
||||
ShaderType::Fragment);
|
||||
fragmentVariant.sourceCode = variantSource;
|
||||
shader->AddPassVariant(pass.name, fragmentVariant);
|
||||
for (const ShaderSubShaderIR& subShader : shaderIR.subShaders) {
|
||||
for (const ShaderPassIR& pass : subShader.passes) {
|
||||
if (pass.isUsePass) {
|
||||
ShaderPass importedPass = {};
|
||||
Containers::String importError;
|
||||
if (!TryResolveUsePass(
|
||||
path,
|
||||
shaderIR.name,
|
||||
pass.usePassShaderName,
|
||||
pass.usePassPassName,
|
||||
localConcretePasses,
|
||||
importedPass,
|
||||
importError)) {
|
||||
return LoadResult(importError);
|
||||
}
|
||||
|
||||
ImportConcretePass(*shader, importedPass);
|
||||
for (const ShaderTagIR& subShaderTag : subShader.tags) {
|
||||
shader->SetPassTag(importedPass.name, subShaderTag.name, subShaderTag.value);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const auto passIt = localConcretePasses.find(ToStdString(pass.name));
|
||||
if (passIt == localConcretePasses.end()) {
|
||||
return LoadResult(
|
||||
Containers::String("Shader authoring lost concrete pass during build: ") + pass.name);
|
||||
}
|
||||
|
||||
ImportConcretePass(*shader, passIt->second);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -190,32 +483,5 @@ LoadResult BuildShaderFromIR(
|
||||
return LoadResult(shader.release());
|
||||
}
|
||||
|
||||
LoadResult LoadLegacySingleStageShader(
|
||||
const Containers::String& path,
|
||||
const std::string& sourceText) {
|
||||
auto shader = std::make_unique<Shader>();
|
||||
shader->m_path = path;
|
||||
shader->m_name = path;
|
||||
shader->m_guid = ResourceGUID::Generate(path);
|
||||
|
||||
const Containers::String ext = GetShaderPathExtension(path).ToLower();
|
||||
if (ext == "hlsl") {
|
||||
shader->SetShaderLanguage(ShaderLanguage::HLSL);
|
||||
} else {
|
||||
shader->SetShaderLanguage(ShaderLanguage::GLSL);
|
||||
}
|
||||
|
||||
shader->SetShaderType(DetectShaderTypeFromPath(path));
|
||||
shader->SetSourceCode(sourceText.c_str());
|
||||
shader->m_isValid = true;
|
||||
shader->m_memorySize =
|
||||
sizeof(Shader) +
|
||||
shader->m_name.Length() +
|
||||
shader->m_path.Length() +
|
||||
shader->GetSourceCode().Length();
|
||||
|
||||
return LoadResult(shader.release());
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
|
||||
Reference in New Issue
Block a user