Close shader authoring pipeline and UsePass dependency tracking

This commit is contained in:
2026-04-07 18:49:37 +08:00
parent 2f9b1696cd
commit 901a6ecc26
12 changed files with 565 additions and 319 deletions

View File

@@ -546,7 +546,7 @@ bool WriteShaderArtifactFile(const fs::path& artifactPath, const Shader& shader)
for (const ShaderPass& pass : shader.GetPasses()) {
WriteString(output, pass.name);
ShaderPassArtifactHeaderV4 passHeader;
ShaderPassArtifactHeaderV5 passHeader;
passHeader.tagCount = static_cast<Core::uint32>(pass.tags.Size());
passHeader.resourceCount = static_cast<Core::uint32>(pass.resources.Size());
passHeader.keywordDeclarationCount = static_cast<Core::uint32>(pass.keywordDeclarations.Size());
@@ -1404,8 +1404,7 @@ Containers::String AssetDatabase::GetImporterNameForPath(const Containers::Strin
if (ext == ".obj" || ext == ".fbx" || ext == ".gltf" || ext == ".glb" || ext == ".dae" || ext == ".stl") {
return Containers::String("ModelImporter");
}
if (ext == ".shader" || ext == ".hlsl" || ext == ".glsl" || ext == ".vert" || ext == ".frag" ||
ext == ".geom" || ext == ".comp") {
if (ext == ".shader") {
return Containers::String("ShaderImporter");
}
if (ext == ".mat" || ext == ".material" || ext == ".json") {

View File

@@ -101,7 +101,7 @@ bool TryResolveBuiltinAssetPathFromAnchor(
return false;
}
bool TryResolveBuiltinShaderAssetPath(
bool TryResolveBuiltinShaderAssetPathFromRelativePath(
const std::filesystem::path& relativePath,
Containers::String& outPath) {
std::filesystem::path resolvedPath;
@@ -159,7 +159,7 @@ const char* GetBuiltinShaderAssetRelativePath(const Containers::String& builtinS
return nullptr;
}
bool TryResolveBuiltinShaderAssetPath(
bool TryResolveBuiltinShaderAssetPathInternal(
const Containers::String& builtinShaderPath,
Containers::String& outPath) {
const char* relativePath = GetBuiltinShaderAssetRelativePath(builtinShaderPath);
@@ -167,7 +167,7 @@ bool TryResolveBuiltinShaderAssetPath(
return false;
}
return TryResolveBuiltinShaderAssetPath(std::filesystem::path(relativePath), outPath);
return TryResolveBuiltinShaderAssetPathFromRelativePath(std::filesystem::path(relativePath), outPath);
}
Shader* LoadBuiltinShaderFromAsset(
@@ -188,7 +188,7 @@ Shader* LoadBuiltinShaderFromAsset(
Shader* TryLoadBuiltinShaderFromAsset(const Containers::String& builtinPath) {
Containers::String assetPath;
if (!TryResolveBuiltinShaderAssetPath(builtinPath, assetPath)) {
if (!TryResolveBuiltinShaderAssetPathInternal(builtinPath, assetPath)) {
return nullptr;
}
@@ -830,6 +830,12 @@ bool TryGetBuiltinShaderPathByShaderName(
return false;
}
bool TryResolveBuiltinShaderAssetPath(
const Containers::String& builtinShaderPath,
Containers::String& outPath) {
return TryResolveBuiltinShaderAssetPathInternal(builtinShaderPath, outPath);
}
const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType) {
switch (primitiveType) {
case BuiltinPrimitiveType::Cube: return "Cube";

View File

@@ -48,6 +48,23 @@ bool ReadShaderArtifactString(
return true;
}
MaterialRenderState ExpandSerializedMaterialRenderStateV4(const SerializedMaterialRenderStateV4& legacyState) {
MaterialRenderState renderState = {};
renderState.blendEnable = legacyState.blendEnable;
renderState.srcBlend = legacyState.srcBlend;
renderState.dstBlend = legacyState.dstBlend;
renderState.srcBlendAlpha = legacyState.srcBlendAlpha;
renderState.dstBlendAlpha = legacyState.dstBlendAlpha;
renderState.blendOp = legacyState.blendOp;
renderState.blendOpAlpha = legacyState.blendOpAlpha;
renderState.colorWriteMask = legacyState.colorWriteMask;
renderState.depthTestEnable = legacyState.depthTestEnable;
renderState.depthWriteEnable = legacyState.depthWriteEnable;
renderState.depthFunc = legacyState.depthFunc;
renderState.cullMode = legacyState.cullMode;
return renderState;
}
} // namespace
LoadResult LoadShaderArtifact(const Containers::String& path) {
@@ -66,9 +83,10 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
const bool isLegacySchema = magic == "XCSHD01" && fileHeader.schemaVersion == 1u;
const bool isSchemaV2 = magic == "XCSHD02" && fileHeader.schemaVersion == 2u;
const bool isSchemaV3 = magic == "XCSHD03" && fileHeader.schemaVersion == 3u;
const bool isSchemaV4 = magic == "XCSHD04" && fileHeader.schemaVersion == 4u;
const bool isCurrentSchema =
magic == "XCSHD04" && fileHeader.schemaVersion == kShaderArtifactSchemaVersion;
if (!isLegacySchema && !isSchemaV2 && !isSchemaV3 && !isCurrentSchema) {
magic == "XCSHD05" && fileHeader.schemaVersion == kShaderArtifactSchemaVersion;
if (!isLegacySchema && !isSchemaV2 && !isSchemaV3 && !isSchemaV4 && !isCurrentSchema) {
return LoadResult("Invalid shader artifact header: " + path);
}
@@ -81,7 +99,7 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
!ReadShaderArtifactString(data, offset, shaderSourcePath)) {
return LoadResult("Failed to parse shader artifact strings: " + path);
}
if (isCurrentSchema &&
if ((isSchemaV4 || isCurrentSchema) &&
!ReadShaderArtifactString(data, offset, shaderFallback)) {
return LoadResult("Failed to parse shader artifact strings: " + path);
}
@@ -142,12 +160,24 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
resourceCount = passHeader.resourceCount;
keywordDeclarationCount = passHeader.keywordDeclarationCount;
variantCount = passHeader.variantCount;
} else {
} else if (isSchemaV4) {
ShaderPassArtifactHeaderV4 passHeader = {};
if (!ReadShaderArtifactValue(data, offset, passHeader)) {
return LoadResult("Failed to read shader artifact passes: " + path);
}
tagCount = passHeader.tagCount;
resourceCount = passHeader.resourceCount;
keywordDeclarationCount = passHeader.keywordDeclarationCount;
variantCount = passHeader.variantCount;
hasFixedFunctionState = passHeader.hasFixedFunctionState;
fixedFunctionState = ExpandSerializedMaterialRenderStateV4(passHeader.fixedFunctionState);
} else {
ShaderPassArtifactHeaderV5 passHeader = {};
if (!ReadShaderArtifactValue(data, offset, passHeader)) {
return LoadResult("Failed to read shader artifact passes: " + path);
}
tagCount = passHeader.tagCount;
resourceCount = passHeader.resourceCount;
keywordDeclarationCount = passHeader.keywordDeclarationCount;
@@ -214,7 +244,7 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
ShaderStageVariant variant = {};
Core::uint64 compiledBinarySize = 0;
Core::uint32 keywordCount = 0;
if (isCurrentSchema) {
if (isSchemaV4 || isCurrentSchema) {
ShaderVariantArtifactHeader variantHeader = {};
if (!ReadShaderArtifactValue(data, offset, variantHeader)) {
return LoadResult("Failed to read shader artifact variants: " + path);
@@ -270,19 +300,6 @@ LoadResult LoadShaderArtifact(const Containers::String& path) {
}
}
if (shader->GetPassCount() == 1) {
const ShaderPass* defaultPass = shader->FindPass("Default");
if (defaultPass != nullptr && defaultPass->variants.Size() == 1u) {
const ShaderStageVariant& variant = defaultPass->variants[0];
if (variant.backend == ShaderBackend::Generic) {
shader->SetShaderType(variant.stage);
shader->SetShaderLanguage(variant.language);
shader->SetSourceCode(variant.sourceCode);
shader->SetCompiledBinary(variant.compiledBinary);
}
}
}
shader->m_isValid = true;
shader->m_memorySize = CalculateShaderMemorySize(*shader);
return LoadResult(shader.release());

View File

@@ -68,8 +68,32 @@ bool CollectShaderAuthoringDependencyPathsRecursive(
}
if (IsBuiltinShaderPath(resolvedUsePassPath)) {
if (seenDependencyPaths.insert(ToStdString(resolvedUsePassPath)).second) {
outDependencies.PushBack(resolvedUsePassPath);
Containers::String builtinAssetPath;
if (!TryResolveBuiltinShaderAssetPath(resolvedUsePassPath, builtinAssetPath)) {
return false;
}
const fs::path normalizedBuiltinAssetPath =
fs::path(builtinAssetPath.CStr()).lexically_normal();
const Containers::String normalizedBuiltinDependency =
normalizedBuiltinAssetPath.generic_string().c_str();
if (!normalizedBuiltinDependency.Empty() &&
seenDependencyPaths.insert(ToStdString(normalizedBuiltinDependency)).second) {
outDependencies.PushBack(normalizedBuiltinDependency);
}
Containers::String referencedSourceText;
if (!ReadShaderTextFile(builtinAssetPath, referencedSourceText)) {
return false;
}
if (!CollectShaderAuthoringDependencyPathsRecursive(
builtinAssetPath,
referencedSourceText.CStr(),
seenShaderPaths,
seenDependencyPaths,
outDependencies)) {
return false;
}
continue;
}

View File

@@ -18,6 +18,11 @@
namespace XCEngine {
namespace Resources {
LoadResult BuildShaderFromIRInternal(
const Containers::String& path,
const ShaderIR& shaderIR,
std::unordered_set<std::string>& activeShaderPathKeys);
namespace {
namespace fs = std::filesystem;
@@ -31,6 +36,25 @@ Containers::String ResolveBuiltinShaderPathByAuthoringName(const Containers::Str
return Containers::String();
}
std::string BuildNormalizedShaderPathKey(const Containers::String& shaderPath) {
if (shaderPath.Empty()) {
return std::string();
}
fs::path normalizedPath(shaderPath.CStr());
if (!normalizedPath.is_absolute()) {
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
if (!resourceRoot.Empty()) {
normalizedPath = fs::path(resourceRoot.CStr()) / normalizedPath;
} else {
std::error_code ec;
normalizedPath = fs::current_path(ec) / normalizedPath;
}
}
return normalizedPath.lexically_normal().generic_string();
}
void AddUniqueSearchRoot(
const fs::path& candidate,
std::vector<fs::path>& searchRoots,
@@ -241,12 +265,73 @@ ShaderPass BuildConcretePass(
return shaderPass;
}
bool TryLoadReferencedUsePassShader(
const Containers::String& referencedShaderPath,
std::unordered_set<std::string>& activeShaderPathKeys,
std::unique_ptr<Shader>& outShader,
Containers::String& outError) {
Containers::String authoringShaderPath = referencedShaderPath;
if (IsBuiltinShaderPath(authoringShaderPath) &&
!TryResolveBuiltinShaderAssetPath(authoringShaderPath, authoringShaderPath)) {
outError =
Containers::String("UsePass failed to resolve builtin shader asset: ") +
referencedShaderPath;
return false;
}
const std::string shaderPathKey = BuildNormalizedShaderPathKey(authoringShaderPath);
if (shaderPathKey.empty()) {
outError =
Containers::String("UsePass could not normalize referenced shader path: ") +
referencedShaderPath;
return false;
}
if (activeShaderPathKeys.find(shaderPathKey) != activeShaderPathKeys.end()) {
outError =
Containers::String("UsePass detected a cyclic shader reference: ") +
authoringShaderPath;
return false;
}
Containers::String sourceText;
if (!ReadShaderTextFile(authoringShaderPath, sourceText)) {
outError =
Containers::String("UsePass failed to read referenced shader: ") +
authoringShaderPath;
return false;
}
ShaderIR referencedShaderIR = {};
Containers::String parseError;
if (!ParseShaderAuthoring(authoringShaderPath, sourceText.CStr(), referencedShaderIR, &parseError)) {
outError = parseError;
return false;
}
activeShaderPathKeys.insert(shaderPathKey);
LoadResult referencedShaderResult =
BuildShaderFromIRInternal(authoringShaderPath, referencedShaderIR, activeShaderPathKeys);
activeShaderPathKeys.erase(shaderPathKey);
if (!referencedShaderResult || referencedShaderResult.resource == nullptr) {
outError =
!referencedShaderResult.errorMessage.Empty()
? referencedShaderResult.errorMessage
: Containers::String("UsePass failed to build referenced shader: ") + authoringShaderPath;
return false;
}
outShader.reset(static_cast<Shader*>(referencedShaderResult.resource));
return true;
}
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,
std::unordered_set<std::string>& activeShaderPathKeys,
ShaderPass& outPass,
Containers::String& outError) {
if (referencedShaderName == currentShaderName) {
@@ -273,15 +358,15 @@ bool TryResolveUsePass(
return false;
}
ShaderLoader loader;
LoadResult referencedShaderResult = loader.Load(referencedShaderPath);
if (!referencedShaderResult || referencedShaderResult.resource == nullptr) {
outError =
Containers::String("UsePass failed to load referenced shader: ") + referencedShaderName;
std::unique_ptr<Shader> referencedShader;
if (!TryLoadReferencedUsePassShader(
referencedShaderPath,
activeShaderPathKeys,
referencedShader,
outError)) {
return false;
}
std::unique_ptr<Shader> referencedShader(static_cast<Shader*>(referencedShaderResult.resource));
const ShaderPass* referencedPass = referencedShader->FindPass(referencedPassName);
if (referencedPass == nullptr) {
outError =
@@ -411,9 +496,10 @@ bool ResolveShaderUsePassPath(
outResolvedPath);
}
LoadResult BuildShaderFromIR(
LoadResult BuildShaderFromIRInternal(
const Containers::String& path,
const ShaderIR& shaderIR) {
const ShaderIR& shaderIR,
std::unordered_set<std::string>& activeShaderPathKeys) {
auto shader = std::make_unique<Shader>();
IResource::ConstructParams params;
params.path = path;
@@ -457,6 +543,7 @@ LoadResult BuildShaderFromIR(
pass.usePassShaderName,
pass.usePassPassName,
localConcretePasses,
activeShaderPathKeys,
importedPass,
importError)) {
return LoadResult(importError);
@@ -483,5 +570,17 @@ LoadResult BuildShaderFromIR(
return LoadResult(shader.release());
}
LoadResult BuildShaderFromIR(
const Containers::String& path,
const ShaderIR& shaderIR) {
std::unordered_set<std::string> activeShaderPathKeys;
const std::string shaderPathKey = BuildNormalizedShaderPathKey(path);
if (!shaderPathKey.empty()) {
activeShaderPathKeys.insert(shaderPathKey);
}
return BuildShaderFromIRInternal(path, shaderIR, activeShaderPathKeys);
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -5,8 +5,6 @@ namespace Resources {
namespace {
const char* kLegacyShaderPassName = "Default";
bool PassDeclaresKeywordInternal(const ShaderPass& pass, const Containers::String& keyword) {
const Containers::String normalizedKeyword = NormalizeShaderKeywordToken(keyword);
if (normalizedKeyword.Empty()) {
@@ -43,38 +41,15 @@ Shader::Shader() = default;
Shader::~Shader() = default;
void Shader::Release() {
m_shaderType = ShaderType::Fragment;
m_language = ShaderLanguage::GLSL;
m_sourceCode.Clear();
m_compiledBinary.Clear();
m_uniforms.Clear();
m_attributes.Clear();
m_properties.Clear();
m_passes.Clear();
m_fallback.Clear();
m_rhiResource = nullptr;
m_isValid = false;
}
void Shader::SetShaderType(ShaderType type) {
m_shaderType = type;
SyncLegacyVariant();
}
void Shader::SetShaderLanguage(ShaderLanguage lang) {
m_language = lang;
SyncLegacyVariant();
}
void Shader::SetSourceCode(const Containers::String& source) {
m_sourceCode = source;
SyncLegacyVariant();
}
void Shader::SetCompiledBinary(const Containers::Array<Core::uint8>& binary) {
m_compiledBinary = binary;
SyncLegacyVariant();
}
void Shader::AddUniform(const ShaderUniform& uniform) {
m_uniforms.PushBack(uniform);
}
@@ -108,6 +83,10 @@ const ShaderPropertyDesc* Shader::FindProperty(const Containers::String& propert
return nullptr;
}
void Shader::SetFallback(const Containers::String& fallback) {
m_fallback = fallback;
}
void Shader::AddPass(const ShaderPass& pass) {
m_passes.PushBack(pass);
}
@@ -272,26 +251,5 @@ ShaderPass& Shader::GetOrCreatePass(const Containers::String& passName) {
return pass;
}
ShaderStageVariant& Shader::GetOrCreateLegacyVariant() {
ShaderPass& pass = GetOrCreatePass(kLegacyShaderPassName);
for (ShaderStageVariant& variant : pass.variants) {
if (variant.backend == ShaderBackend::Generic) {
return variant;
}
}
ShaderStageVariant& variant = pass.variants.EmplaceBack();
variant.backend = ShaderBackend::Generic;
return variant;
}
void Shader::SyncLegacyVariant() {
ShaderStageVariant& variant = GetOrCreateLegacyVariant();
variant.stage = m_shaderType;
variant.language = m_language;
variant.sourceCode = m_sourceCode;
variant.compiledBinary = m_compiledBinary;
}
} // namespace Resources
} // namespace XCEngine

View File

@@ -4,7 +4,6 @@
#include "Internal/ShaderAuthoringLoader.h"
#include "ShaderAuthoringParser.h"
#include "Internal/ShaderFileUtils.h"
#include "Internal/ShaderManifestLoader.h"
#include "Internal/ShaderRuntimeBuildUtils.h"
#include <XCEngine/Core/Asset/ResourceTypes.h>
@@ -21,12 +20,6 @@ ShaderLoader::~ShaderLoader() = default;
Containers::Array<Containers::String> ShaderLoader::GetSupportedExtensions() const {
Containers::Array<Containers::String> extensions;
extensions.PushBack("vert");
extensions.PushBack("frag");
extensions.PushBack("geom");
extensions.PushBack("comp");
extensions.PushBack("glsl");
extensions.PushBack("hlsl");
extensions.PushBack("shader");
extensions.PushBack("xcshader");
return extensions;
@@ -38,9 +31,7 @@ bool ShaderLoader::CanLoad(const Containers::String& path) const {
}
const Containers::String ext = GetExtension(path).ToLower();
return ext == "vert" || ext == "frag" || ext == "geom" ||
ext == "comp" || ext == "glsl" || ext == "hlsl" ||
ext == "shader" || ext == "xcshader";
return ext == "shader" || ext == "xcshader";
}
LoadResult ShaderLoader::Load(const Containers::String& path, const ImportSettings* settings) {
@@ -61,14 +52,15 @@ LoadResult ShaderLoader::Load(const Containers::String& path, const ImportSettin
}
const std::string sourceText = ToStdStringFromBytes(data);
if (ext == "shader" && LooksLikeShaderManifest(sourceText)) {
return LoadShaderManifest(path, sourceText);
}
if (ext == "shader" && LooksLikeShaderAuthoring(sourceText)) {
if (ext == "shader") {
if (!LooksLikeShaderAuthoring(sourceText)) {
return LoadResult("Shader authoring file must start with a Shader declaration: " + path);
}
return LoadShaderAuthoring(path, sourceText);
}
return LoadLegacySingleStageShader(path, sourceText);
return LoadResult("Unsupported shader source format: " + path);
}
ImportSettings* ShaderLoader::GetDefaultSettings() const {
@@ -95,13 +87,11 @@ bool ShaderLoader::CollectSourceDependencies(
}
const std::string sourceText = ToStdStringFromBytes(data);
if (!LooksLikeShaderManifest(sourceText)) {
return LooksLikeShaderAuthoring(sourceText)
? CollectShaderAuthoringDependencyPaths(path, sourceText, outDependencies)
: true;
if (!LooksLikeShaderAuthoring(sourceText)) {
return false;
}
return CollectShaderManifestDependencyPaths(path, sourceText, outDependencies);
return CollectShaderAuthoringDependencyPaths(path, sourceText, outDependencies);
}
ShaderType ShaderLoader::DetectShaderType(const Containers::String& path, const Containers::String& source) {