Close shader authoring pipeline and UsePass dependency tracking
This commit is contained in:
@@ -22,6 +22,9 @@ bool IsBuiltinTexturePath(const Containers::String& path);
|
||||
bool TryGetBuiltinShaderPathByShaderName(
|
||||
const Containers::String& shaderName,
|
||||
Containers::String& outPath);
|
||||
bool TryResolveBuiltinShaderAssetPath(
|
||||
const Containers::String& builtinShaderPath,
|
||||
Containers::String& outPath);
|
||||
|
||||
const char* GetBuiltinPrimitiveDisplayName(BuiltinPrimitiveType primitiveType);
|
||||
Containers::String GetBuiltinPrimitiveMeshPath(BuiltinPrimitiveType primitiveType);
|
||||
|
||||
@@ -31,8 +31,6 @@ enum class ShaderBackend : Core::uint8 {
|
||||
Vulkan
|
||||
};
|
||||
|
||||
// Keep shader property kinds close to Unity's public shader syntax so the
|
||||
// runtime contract can be reused when ShaderLab-compatible parsing is added.
|
||||
enum class ShaderPropertyType : Core::uint8 {
|
||||
Float = 0,
|
||||
Range,
|
||||
@@ -119,18 +117,6 @@ public:
|
||||
size_t GetMemorySize() const override { return m_memorySize; }
|
||||
void Release() override;
|
||||
|
||||
void SetShaderType(ShaderType type);
|
||||
ShaderType GetShaderType() const { return m_shaderType; }
|
||||
|
||||
void SetShaderLanguage(ShaderLanguage lang);
|
||||
ShaderLanguage GetShaderLanguage() const { return m_language; }
|
||||
|
||||
void SetSourceCode(const Containers::String& source);
|
||||
const Containers::String& GetSourceCode() const { return m_sourceCode; }
|
||||
|
||||
void SetCompiledBinary(const Containers::Array<Core::uint8>& binary);
|
||||
const Containers::Array<Core::uint8>& GetCompiledBinary() const { return m_compiledBinary; }
|
||||
|
||||
void AddUniform(const ShaderUniform& uniform);
|
||||
const Containers::Array<ShaderUniform>& GetUniforms() const { return m_uniforms; }
|
||||
|
||||
@@ -181,14 +167,6 @@ public:
|
||||
|
||||
private:
|
||||
ShaderPass& GetOrCreatePass(const Containers::String& passName);
|
||||
ShaderStageVariant& GetOrCreateLegacyVariant();
|
||||
void SyncLegacyVariant();
|
||||
|
||||
ShaderType m_shaderType = ShaderType::Fragment;
|
||||
ShaderLanguage m_language = ShaderLanguage::GLSL;
|
||||
|
||||
Containers::String m_sourceCode;
|
||||
Containers::Array<Core::uint8> m_compiledBinary;
|
||||
|
||||
Containers::Array<ShaderUniform> m_uniforms;
|
||||
Containers::Array<ShaderAttribute> m_attributes;
|
||||
|
||||
@@ -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") {
|
||||
|
||||
@@ -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";
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -67,64 +67,45 @@ void WriteTextFile(const std::filesystem::path& path, const std::string& content
|
||||
ASSERT_TRUE(static_cast<bool>(output));
|
||||
}
|
||||
|
||||
std::filesystem::path WriteSchemaMaterialShaderManifest(const std::filesystem::path& rootPath) {
|
||||
std::filesystem::path WriteSchemaMaterialShaderAuthoring(const std::filesystem::path& rootPath) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path shaderDir = rootPath / "Shaders";
|
||||
fs::create_directories(shaderDir);
|
||||
|
||||
WriteTextFile(shaderDir / "schema.vert.glsl", "#version 430\nvoid main() {}\n");
|
||||
WriteTextFile(shaderDir / "schema.frag.glsl", "#version 430\nvoid main() {}\n");
|
||||
|
||||
const fs::path manifestPath = shaderDir / "schema.shader";
|
||||
std::ofstream manifest(manifestPath, std::ios::binary | std::ios::trunc);
|
||||
EXPECT_TRUE(manifest.is_open());
|
||||
if (!manifest.is_open()) {
|
||||
const fs::path shaderPath = shaderDir / "schema.shader";
|
||||
std::ofstream shader(shaderPath, std::ios::binary | std::ios::trunc);
|
||||
EXPECT_TRUE(shader.is_open());
|
||||
if (!shader.is_open()) {
|
||||
return {};
|
||||
}
|
||||
manifest << "{\n";
|
||||
manifest << " \"name\": \"SchemaMaterialShader\",\n";
|
||||
manifest << " \"properties\": [\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"_BaseColor\",\n";
|
||||
manifest << " \"displayName\": \"Base Color\",\n";
|
||||
manifest << " \"type\": \"Color\",\n";
|
||||
manifest << " \"defaultValue\": \"(1,0.5,0.25,1)\",\n";
|
||||
manifest << " \"semantic\": \"BaseColor\"\n";
|
||||
manifest << " },\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"_Metallic\",\n";
|
||||
manifest << " \"displayName\": \"Metallic\",\n";
|
||||
manifest << " \"type\": \"Float\",\n";
|
||||
manifest << " \"defaultValue\": \"0.7\"\n";
|
||||
manifest << " },\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"_Mode\",\n";
|
||||
manifest << " \"displayName\": \"Mode\",\n";
|
||||
manifest << " \"type\": \"Int\",\n";
|
||||
manifest << " \"defaultValue\": \"2\"\n";
|
||||
manifest << " },\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"_MainTex\",\n";
|
||||
manifest << " \"displayName\": \"Main Tex\",\n";
|
||||
manifest << " \"type\": \"Texture2D\",\n";
|
||||
manifest << " \"defaultValue\": \"white\",\n";
|
||||
manifest << " \"semantic\": \"BaseColorTexture\"\n";
|
||||
manifest << " }\n";
|
||||
manifest << " ],\n";
|
||||
manifest << " \"passes\": [\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"ForwardLit\",\n";
|
||||
manifest << " \"variants\": [\n";
|
||||
manifest << " { \"stage\": \"Vertex\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"schema.vert.glsl\" },\n";
|
||||
manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"schema.frag.glsl\" }\n";
|
||||
manifest << " ]\n";
|
||||
manifest << " }\n";
|
||||
manifest << " ]\n";
|
||||
manifest << "}\n";
|
||||
EXPECT_TRUE(static_cast<bool>(manifest));
|
||||
shader << R"(Shader "SchemaMaterialShader"
|
||||
{
|
||||
Properties
|
||||
{
|
||||
_BaseColor ("Base Color", Color) = (1,0.5,0.25,1) [Semantic(BaseColor)]
|
||||
_Metallic ("Metallic", Float) = 0.7
|
||||
_Mode ("Mode", Int) = 2
|
||||
_MainTex ("Main Tex", 2D) = "white" [Semantic(BaseColorTexture)]
|
||||
}
|
||||
SubShader
|
||||
{
|
||||
Pass
|
||||
{
|
||||
Name "ForwardLit"
|
||||
HLSLPROGRAM
|
||||
#pragma vertex MainVS
|
||||
#pragma fragment MainPS
|
||||
float4 MainVS() : SV_POSITION { return 0; }
|
||||
float4 MainPS() : SV_TARGET { return 1; }
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
EXPECT_TRUE(static_cast<bool>(shader));
|
||||
|
||||
return manifestPath;
|
||||
return shaderPath;
|
||||
}
|
||||
|
||||
std::filesystem::path WriteKeywordMaterialShaderAuthoring(const std::filesystem::path& rootPath) {
|
||||
@@ -200,15 +181,34 @@ TEST(MaterialLoader, ResourceManagerRegistersMaterialAndShaderLoaders) {
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) {
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
const std::filesystem::path shaderPath =
|
||||
std::filesystem::current_path() / "material_loader_valid_shader.hlsl";
|
||||
std::filesystem::current_path() / "material_loader_valid_shader.shader";
|
||||
const std::filesystem::path materialPath =
|
||||
std::filesystem::current_path() / "material_loader_valid.material";
|
||||
|
||||
{
|
||||
std::ofstream shaderFile(shaderPath);
|
||||
ASSERT_TRUE(shaderFile.is_open());
|
||||
shaderFile << "float4 MainPS() : SV_TARGET { return float4(1, 1, 1, 1); }";
|
||||
shaderFile << R"(Shader "Test/ValidMaterial"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
Pass
|
||||
{
|
||||
Name "ForwardLit"
|
||||
HLSLPROGRAM
|
||||
#pragma vertex MainVS
|
||||
#pragma fragment MainPS
|
||||
float4 MainVS() : SV_POSITION { return 0; }
|
||||
float4 MainPS() : SV_TARGET { return float4(1, 1, 1, 1); }
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
)";
|
||||
}
|
||||
|
||||
{
|
||||
@@ -243,7 +243,7 @@ TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) {
|
||||
EXPECT_TRUE(material->IsValid());
|
||||
EXPECT_NE(material->GetShader(), nullptr);
|
||||
EXPECT_EQ(material->GetRenderQueue(), static_cast<XCEngine::Core::int32>(MaterialRenderQueue::Transparent));
|
||||
EXPECT_EQ(material->GetLegacyShaderPassHint(), "ForwardLit");
|
||||
EXPECT_TRUE(material->GetLegacyShaderPassHint().Empty());
|
||||
EXPECT_EQ(material->GetTag("LightMode"), "ForwardBase");
|
||||
EXPECT_EQ(material->GetTag("RenderType"), "Transparent");
|
||||
EXPECT_EQ(material->GetRenderState().cullMode, MaterialCullMode::Back);
|
||||
@@ -255,6 +255,7 @@ TEST(MaterialLoader, LoadValidMaterialParsesRenderMetadata) {
|
||||
EXPECT_TRUE(material->HasRenderStateOverride());
|
||||
|
||||
delete material;
|
||||
manager.Shutdown();
|
||||
std::remove(materialPath.string().c_str());
|
||||
std::remove(shaderPath.string().c_str());
|
||||
}
|
||||
@@ -282,55 +283,103 @@ TEST(MaterialLoader, LoadMaterialWithoutRenderStateLeavesOverrideDisabled) {
|
||||
std::remove(materialPath.string().c_str());
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, LoadMaterialWithShaderManifestResolvesShaderPass) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
const fs::path shaderRoot = fs::temp_directory_path() / "xc_material_shader_manifest_test";
|
||||
const fs::path shaderDir = shaderRoot / "Shaders";
|
||||
const fs::path manifestPath = shaderDir / "lit.shader";
|
||||
const fs::path materialPath = shaderRoot / "manifest.material";
|
||||
|
||||
fs::remove_all(shaderRoot);
|
||||
fs::create_directories(shaderDir);
|
||||
|
||||
{
|
||||
std::ofstream vertexFile(shaderDir / "lit.vert.glsl");
|
||||
ASSERT_TRUE(vertexFile.is_open());
|
||||
vertexFile << "#version 430\n// MATERIAL_MANIFEST_GL_VS\nvoid main() {}\n";
|
||||
}
|
||||
|
||||
{
|
||||
std::ofstream fragmentFile(shaderDir / "lit.frag.glsl");
|
||||
ASSERT_TRUE(fragmentFile.is_open());
|
||||
fragmentFile << "#version 430\n// MATERIAL_MANIFEST_GL_PS\nvoid main() {}\n";
|
||||
}
|
||||
|
||||
{
|
||||
std::ofstream manifestFile(manifestPath);
|
||||
ASSERT_TRUE(manifestFile.is_open());
|
||||
manifestFile << "{\n";
|
||||
manifestFile << " \"name\": \"ManifestLit\",\n";
|
||||
manifestFile << " \"passes\": [\n";
|
||||
manifestFile << " {\n";
|
||||
manifestFile << " \"name\": \"ForwardLit\",\n";
|
||||
manifestFile << " \"tags\": { \"LightMode\": \"ForwardBase\" },\n";
|
||||
manifestFile << " \"variants\": [\n";
|
||||
manifestFile << " { \"stage\": \"Vertex\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"lit.vert.glsl\" },\n";
|
||||
manifestFile << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"lit.frag.glsl\" }\n";
|
||||
manifestFile << " ]\n";
|
||||
manifestFile << " }\n";
|
||||
manifestFile << " ]\n";
|
||||
manifestFile << "}\n";
|
||||
}
|
||||
TEST(MaterialLoader, LoadMaterialParsesOffsetAndStencilRenderState) {
|
||||
const std::filesystem::path materialPath =
|
||||
std::filesystem::current_path() / "material_loader_offset_stencil.material";
|
||||
|
||||
{
|
||||
std::ofstream materialFile(materialPath);
|
||||
ASSERT_TRUE(materialFile.is_open());
|
||||
materialFile << "{\n";
|
||||
materialFile << " \"shader\": \"" << manifestPath.generic_string() << "\",\n";
|
||||
materialFile << " \"renderState\": {\n";
|
||||
materialFile << " \"offset\": [1.5, 2],\n";
|
||||
materialFile << " \"stencil\": {\n";
|
||||
materialFile << " \"ref\": 7,\n";
|
||||
materialFile << " \"readMask\": 63,\n";
|
||||
materialFile << " \"writeMask\": 31,\n";
|
||||
materialFile << " \"comp\": \"Equal\",\n";
|
||||
materialFile << " \"pass\": \"Replace\",\n";
|
||||
materialFile << " \"fail\": \"Keep\",\n";
|
||||
materialFile << " \"zFail\": \"IncrSat\",\n";
|
||||
materialFile << " \"compBack\": \"NotEqual\",\n";
|
||||
materialFile << " \"passBack\": \"DecrWrap\",\n";
|
||||
materialFile << " \"failBack\": \"Invert\",\n";
|
||||
materialFile << " \"zFailBack\": \"Zero\"\n";
|
||||
materialFile << " }\n";
|
||||
materialFile << " }\n";
|
||||
materialFile << "}\n";
|
||||
}
|
||||
|
||||
MaterialLoader loader;
|
||||
LoadResult result = loader.Load(materialPath.string().c_str());
|
||||
ASSERT_TRUE(result);
|
||||
ASSERT_NE(result.resource, nullptr);
|
||||
|
||||
auto* material = static_cast<Material*>(result.resource);
|
||||
ASSERT_NE(material, nullptr);
|
||||
EXPECT_FLOAT_EQ(material->GetRenderState().depthBiasFactor, 1.5f);
|
||||
EXPECT_EQ(material->GetRenderState().depthBiasUnits, 2);
|
||||
EXPECT_TRUE(material->GetRenderState().stencil.enabled);
|
||||
EXPECT_EQ(material->GetRenderState().stencil.reference, 7u);
|
||||
EXPECT_EQ(material->GetRenderState().stencil.readMask, 63u);
|
||||
EXPECT_EQ(material->GetRenderState().stencil.writeMask, 31u);
|
||||
EXPECT_EQ(material->GetRenderState().stencil.front.func, MaterialComparisonFunc::Equal);
|
||||
EXPECT_EQ(material->GetRenderState().stencil.front.passOp, MaterialStencilOp::Replace);
|
||||
EXPECT_EQ(material->GetRenderState().stencil.front.depthFailOp, MaterialStencilOp::IncrSat);
|
||||
EXPECT_EQ(material->GetRenderState().stencil.back.func, MaterialComparisonFunc::NotEqual);
|
||||
EXPECT_EQ(material->GetRenderState().stencil.back.passOp, MaterialStencilOp::DecrWrap);
|
||||
EXPECT_EQ(material->GetRenderState().stencil.back.failOp, MaterialStencilOp::Invert);
|
||||
EXPECT_EQ(material->GetRenderState().stencil.back.depthFailOp, MaterialStencilOp::Zero);
|
||||
|
||||
delete material;
|
||||
std::remove(materialPath.string().c_str());
|
||||
}
|
||||
|
||||
TEST(MaterialLoader, LoadMaterialWithAuthoringShaderResolvesShaderPass) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
ResourceManager& manager = ResourceManager::Get();
|
||||
manager.Initialize();
|
||||
|
||||
const fs::path shaderRoot = fs::temp_directory_path() / "xc_material_shader_authoring_test";
|
||||
const fs::path shaderDir = shaderRoot / "Shaders";
|
||||
const fs::path shaderPath = shaderDir / "lit.shader";
|
||||
const fs::path materialPath = shaderRoot / "authoring.material";
|
||||
|
||||
fs::remove_all(shaderRoot);
|
||||
fs::create_directories(shaderDir);
|
||||
WriteTextFile(
|
||||
shaderPath,
|
||||
R"(Shader "AuthoringLit"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
Pass
|
||||
{
|
||||
Name "ForwardLit"
|
||||
Tags { "LightMode" = "ForwardBase" }
|
||||
HLSLPROGRAM
|
||||
#pragma vertex MainVS
|
||||
#pragma fragment MainPS
|
||||
float4 MainVS() : SV_POSITION
|
||||
{
|
||||
return 0; // MATERIAL_AUTHORING_VS
|
||||
}
|
||||
float4 MainPS() : SV_TARGET
|
||||
{
|
||||
return 1; // MATERIAL_AUTHORING_PS
|
||||
}
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
||||
{
|
||||
std::ofstream materialFile(materialPath);
|
||||
ASSERT_TRUE(materialFile.is_open());
|
||||
materialFile << "{\n";
|
||||
materialFile << " \"shader\": \"" << shaderPath.generic_string() << "\",\n";
|
||||
materialFile << " \"shaderPass\": \"ForwardLit\",\n";
|
||||
materialFile << " \"renderQueue\": \"Geometry\"\n";
|
||||
materialFile << "}\n";
|
||||
@@ -349,7 +398,7 @@ TEST(MaterialLoader, LoadMaterialWithShaderManifestResolvesShaderPass) {
|
||||
const ShaderStageVariant* vertexVariant =
|
||||
material->GetShader()->FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::OpenGL);
|
||||
ASSERT_NE(vertexVariant, nullptr);
|
||||
EXPECT_NE(std::string(vertexVariant->sourceCode.CStr()).find("MATERIAL_MANIFEST_GL_VS"), std::string::npos);
|
||||
EXPECT_NE(std::string(vertexVariant->sourceCode.CStr()).find("MATERIAL_AUTHORING_VS"), std::string::npos);
|
||||
|
||||
delete material;
|
||||
manager.Shutdown();
|
||||
@@ -362,7 +411,7 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectAppliesTypedOverrides) {
|
||||
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_override_test";
|
||||
fs::remove_all(rootPath);
|
||||
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
|
||||
ASSERT_FALSE(shaderPath.empty());
|
||||
const fs::path materialPath = rootPath / "override.material";
|
||||
|
||||
@@ -437,7 +486,7 @@ TEST(MaterialLoader, RejectsUnknownPropertyAgainstShaderSchema) {
|
||||
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_unknown_test";
|
||||
fs::remove_all(rootPath);
|
||||
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
|
||||
ASSERT_FALSE(shaderPath.empty());
|
||||
const fs::path materialPath = rootPath / "unknown_property.material";
|
||||
|
||||
@@ -463,7 +512,7 @@ TEST(MaterialLoader, RejectsTypeMismatchAgainstShaderSchema) {
|
||||
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_mismatch_test";
|
||||
fs::remove_all(rootPath);
|
||||
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
|
||||
ASSERT_FALSE(shaderPath.empty());
|
||||
const fs::path materialPath = rootPath / "type_mismatch.material";
|
||||
|
||||
@@ -489,7 +538,7 @@ TEST(MaterialLoader, LoadMaterialWithPropertiesObjectPreservesShaderDefaultsForO
|
||||
const fs::path rootPath = fs::temp_directory_path() / "xc_material_loader_properties_defaults_test";
|
||||
fs::remove_all(rootPath);
|
||||
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
|
||||
ASSERT_FALSE(shaderPath.empty());
|
||||
const fs::path materialPath = rootPath / "defaults.material";
|
||||
|
||||
@@ -526,7 +575,7 @@ TEST(MaterialLoader, LoadMaterialMapsLegacySemanticKeysIntoShaderSchemaPropertie
|
||||
fs::remove_all(rootPath);
|
||||
fs::create_directories(rootPath);
|
||||
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
|
||||
ASSERT_FALSE(shaderPath.empty());
|
||||
const fs::path materialPath = rootPath / "semantic_alias.material";
|
||||
|
||||
@@ -604,7 +653,7 @@ TEST(MaterialLoader, RejectsUnknownTextureBindingAgainstShaderSchema) {
|
||||
fs::remove_all(rootPath);
|
||||
fs::create_directories(rootPath);
|
||||
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderManifest(rootPath);
|
||||
const fs::path shaderPath = WriteSchemaMaterialShaderAuthoring(rootPath);
|
||||
ASSERT_FALSE(shaderPath.empty());
|
||||
const fs::path materialPath = rootPath / "unknown_texture.material";
|
||||
|
||||
@@ -985,31 +1034,31 @@ TEST(MaterialLoader, AssetDatabaseReimportsMaterialWhenShaderDependencyChanges)
|
||||
const fs::path projectRoot = fs::temp_directory_path() / "xc_material_shader_dependency_reimport_test";
|
||||
const fs::path assetsDir = projectRoot / "Assets";
|
||||
const fs::path shaderDir = assetsDir / "Shaders";
|
||||
const fs::path shaderManifestPath = shaderDir / "lit.shader";
|
||||
const fs::path shaderPath = shaderDir / "lit.shader";
|
||||
const fs::path materialPath = assetsDir / "textured.material";
|
||||
|
||||
fs::remove_all(projectRoot);
|
||||
fs::create_directories(shaderDir);
|
||||
|
||||
WriteTextFile(shaderDir / "lit.vert.glsl", "#version 430\nvoid main() {}\n");
|
||||
WriteTextFile(shaderDir / "lit.frag.glsl", "#version 430\nvoid main() {}\n");
|
||||
|
||||
WriteTextFile(
|
||||
shaderPath,
|
||||
R"(Shader "MaterialDependencyShader"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
std::ofstream manifest(shaderManifestPath);
|
||||
ASSERT_TRUE(manifest.is_open());
|
||||
manifest << "{\n";
|
||||
manifest << " \"name\": \"MaterialDependencyShader\",\n";
|
||||
manifest << " \"passes\": [\n";
|
||||
manifest << " {\n";
|
||||
manifest << " \"name\": \"ForwardLit\",\n";
|
||||
manifest << " \"variants\": [\n";
|
||||
manifest << " { \"stage\": \"Vertex\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"lit.vert.glsl\" },\n";
|
||||
manifest << " { \"stage\": \"Fragment\", \"backend\": \"OpenGL\", \"language\": \"GLSL\", \"source\": \"lit.frag.glsl\" }\n";
|
||||
manifest << " ]\n";
|
||||
manifest << " }\n";
|
||||
manifest << " ]\n";
|
||||
manifest << "}\n";
|
||||
Pass
|
||||
{
|
||||
Name "ForwardLit"
|
||||
HLSLPROGRAM
|
||||
#pragma vertex MainVS
|
||||
#pragma fragment MainPS
|
||||
float4 MainVS() : SV_POSITION { return 0; }
|
||||
float4 MainPS() : SV_TARGET { return 1; }
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
||||
{
|
||||
std::ofstream materialFile(materialPath);
|
||||
@@ -1034,10 +1083,10 @@ TEST(MaterialLoader, AssetDatabaseReimportsMaterialWhenShaderDependencyChanges)
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
{
|
||||
std::ofstream manifest(shaderManifestPath, std::ios::app);
|
||||
ASSERT_TRUE(manifest.is_open());
|
||||
manifest << "\n";
|
||||
ASSERT_TRUE(static_cast<bool>(manifest));
|
||||
std::ofstream shaderFile(shaderPath, std::ios::app);
|
||||
ASSERT_TRUE(shaderFile.is_open());
|
||||
shaderFile << "\n// force shader dependency reimport\n";
|
||||
ASSERT_TRUE(static_cast<bool>(shaderFile));
|
||||
}
|
||||
|
||||
database.Initialize(projectRoot.string().c_str());
|
||||
|
||||
@@ -10,8 +10,7 @@ namespace {
|
||||
|
||||
TEST(Shader, DefaultConstructor) {
|
||||
Shader shader;
|
||||
EXPECT_EQ(shader.GetShaderType(), ShaderType::Fragment);
|
||||
EXPECT_EQ(shader.GetShaderLanguage(), ShaderLanguage::GLSL);
|
||||
EXPECT_EQ(shader.GetPassCount(), 0u);
|
||||
EXPECT_FALSE(shader.IsValid());
|
||||
EXPECT_EQ(shader.GetMemorySize(), 0u);
|
||||
}
|
||||
@@ -21,64 +20,42 @@ TEST(Shader, GetType) {
|
||||
EXPECT_EQ(shader.GetType(), ResourceType::Shader);
|
||||
}
|
||||
|
||||
TEST(Shader, SetGetShaderType) {
|
||||
TEST(Shader, AddPassVariantStoresStageLanguageAndPayload) {
|
||||
Shader shader;
|
||||
|
||||
shader.SetShaderType(ShaderType::Vertex);
|
||||
EXPECT_EQ(shader.GetShaderType(), ShaderType::Vertex);
|
||||
|
||||
shader.SetShaderType(ShaderType::Fragment);
|
||||
EXPECT_EQ(shader.GetShaderType(), ShaderType::Fragment);
|
||||
|
||||
shader.SetShaderType(ShaderType::Geometry);
|
||||
EXPECT_EQ(shader.GetShaderType(), ShaderType::Geometry);
|
||||
|
||||
shader.SetShaderType(ShaderType::Compute);
|
||||
EXPECT_EQ(shader.GetShaderType(), ShaderType::Compute);
|
||||
}
|
||||
ShaderStageVariant variant = {};
|
||||
variant.stage = ShaderType::Vertex;
|
||||
variant.language = ShaderLanguage::HLSL;
|
||||
variant.backend = ShaderBackend::D3D12;
|
||||
variant.entryPoint = "MainVS";
|
||||
variant.profile = "vs_5_0";
|
||||
variant.sourceCode = "float4 MainVS() : SV_POSITION { return 0; }";
|
||||
variant.compiledBinary = { 0x00, 0x01, 0x02, 0x03 };
|
||||
|
||||
TEST(Shader, SetGetShaderLanguage) {
|
||||
Shader shader;
|
||||
|
||||
shader.SetShaderLanguage(ShaderLanguage::GLSL);
|
||||
EXPECT_EQ(shader.GetShaderLanguage(), ShaderLanguage::GLSL);
|
||||
|
||||
shader.SetShaderLanguage(ShaderLanguage::HLSL);
|
||||
EXPECT_EQ(shader.GetShaderLanguage(), ShaderLanguage::HLSL);
|
||||
|
||||
shader.SetShaderLanguage(ShaderLanguage::SPIRV);
|
||||
EXPECT_EQ(shader.GetShaderLanguage(), ShaderLanguage::SPIRV);
|
||||
}
|
||||
shader.AddPassVariant("ForwardLit", variant);
|
||||
|
||||
TEST(Shader, SetGetSourceCode) {
|
||||
Shader shader;
|
||||
const char* source = "#version 330 core\nvoid main() {}";
|
||||
|
||||
shader.SetSourceCode(source);
|
||||
EXPECT_EQ(shader.GetSourceCode(), source);
|
||||
}
|
||||
|
||||
TEST(Shader, SetGetCompiledBinary) {
|
||||
Shader shader;
|
||||
XCEngine::Containers::Array<XCEngine::Core::uint8> binary = { 0x00, 0x01, 0x02, 0x03 };
|
||||
|
||||
shader.SetCompiledBinary(binary);
|
||||
EXPECT_EQ(shader.GetCompiledBinary().Size(), 4u);
|
||||
EXPECT_EQ(shader.GetCompiledBinary()[0], 0x00);
|
||||
EXPECT_EQ(shader.GetCompiledBinary()[3], 0x03);
|
||||
const ShaderStageVariant* storedVariant =
|
||||
shader.FindVariant("ForwardLit", ShaderType::Vertex, ShaderBackend::D3D12);
|
||||
ASSERT_NE(storedVariant, nullptr);
|
||||
EXPECT_EQ(storedVariant->stage, ShaderType::Vertex);
|
||||
EXPECT_EQ(storedVariant->language, ShaderLanguage::HLSL);
|
||||
EXPECT_EQ(storedVariant->entryPoint, "MainVS");
|
||||
EXPECT_EQ(storedVariant->profile, "vs_5_0");
|
||||
EXPECT_EQ(storedVariant->sourceCode, "float4 MainVS() : SV_POSITION { return 0; }");
|
||||
ASSERT_EQ(storedVariant->compiledBinary.Size(), 4u);
|
||||
EXPECT_EQ(storedVariant->compiledBinary[3], 0x03);
|
||||
}
|
||||
|
||||
TEST(Shader, AddGetUniforms) {
|
||||
Shader shader;
|
||||
|
||||
|
||||
ShaderUniform uniform1;
|
||||
uniform1.name = "uModelView";
|
||||
uniform1.location = 0;
|
||||
uniform1.size = 1;
|
||||
uniform1.type = 4;
|
||||
|
||||
|
||||
shader.AddUniform(uniform1);
|
||||
|
||||
|
||||
const auto& uniforms = shader.GetUniforms();
|
||||
EXPECT_EQ(uniforms.Size(), 1u);
|
||||
EXPECT_EQ(uniforms[0].name, "uModelView");
|
||||
@@ -86,36 +63,20 @@ TEST(Shader, AddGetUniforms) {
|
||||
|
||||
TEST(Shader, AddGetAttributes) {
|
||||
Shader shader;
|
||||
|
||||
|
||||
ShaderAttribute attr1;
|
||||
attr1.name = "aPosition";
|
||||
attr1.location = 0;
|
||||
attr1.size = 1;
|
||||
attr1.type = 3;
|
||||
|
||||
|
||||
shader.AddAttribute(attr1);
|
||||
|
||||
|
||||
const auto& attributes = shader.GetAttributes();
|
||||
EXPECT_EQ(attributes.Size(), 1u);
|
||||
EXPECT_EQ(attributes[0].name, "aPosition");
|
||||
}
|
||||
|
||||
TEST(Shader, LegacySingleStageStateSyncsIntoDefaultPassVariant) {
|
||||
Shader shader;
|
||||
shader.SetShaderType(ShaderType::Vertex);
|
||||
shader.SetShaderLanguage(ShaderLanguage::HLSL);
|
||||
shader.SetSourceCode("float4 MainVS() : SV_POSITION { return 0; }");
|
||||
|
||||
ASSERT_EQ(shader.GetPassCount(), 1u);
|
||||
const ShaderPass* pass = shader.FindPass("Default");
|
||||
ASSERT_NE(pass, nullptr);
|
||||
ASSERT_EQ(pass->variants.Size(), 1u);
|
||||
EXPECT_EQ(pass->variants[0].stage, ShaderType::Vertex);
|
||||
EXPECT_EQ(pass->variants[0].language, ShaderLanguage::HLSL);
|
||||
EXPECT_EQ(pass->variants[0].backend, ShaderBackend::Generic);
|
||||
EXPECT_EQ(pass->variants[0].sourceCode, "float4 MainVS() : SV_POSITION { return 0; }");
|
||||
}
|
||||
|
||||
TEST(Shader, FindsBackendSpecificVariantAndFallsBackToGeneric) {
|
||||
Shader shader;
|
||||
|
||||
@@ -309,7 +270,6 @@ TEST(Shader, StoresPassKeywordDeclarationsAndQueriesDeclaredKeywords) {
|
||||
|
||||
TEST(Shader, ReleaseClearsPassRuntimeData) {
|
||||
Shader shader;
|
||||
shader.SetSourceCode("void main() {}");
|
||||
ShaderPropertyDesc property = {};
|
||||
property.name = "_BaseColor";
|
||||
property.type = ShaderPropertyType::Color;
|
||||
@@ -323,8 +283,6 @@ TEST(Shader, ReleaseClearsPassRuntimeData) {
|
||||
|
||||
EXPECT_EQ(shader.GetProperties().Size(), 0u);
|
||||
EXPECT_EQ(shader.GetPassCount(), 0u);
|
||||
EXPECT_EQ(shader.GetSourceCode(), "");
|
||||
EXPECT_EQ(shader.GetCompiledBinary().Size(), 0u);
|
||||
EXPECT_FALSE(shader.IsValid());
|
||||
}
|
||||
|
||||
|
||||
@@ -1193,6 +1193,171 @@ TEST(ShaderLoader, AssetDatabaseReimportsShaderWhenUsePassDependencyChanges) {
|
||||
fs::remove_all(projectRoot);
|
||||
}
|
||||
|
||||
TEST(ShaderLoader, AssetDatabaseReimportsShaderWhenBuiltinUsePassDependencyChanges) {
|
||||
namespace fs = std::filesystem;
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
const fs::path sandboxRoot =
|
||||
fs::temp_directory_path() / "xc_shader_authoring_builtin_usepass_reimport";
|
||||
const fs::path projectRoot = sandboxRoot / "Project";
|
||||
const fs::path shaderDir = projectRoot / "Assets" / "Shaders";
|
||||
const fs::path mainShaderPath = shaderDir / "main.shader";
|
||||
const fs::path builtinShaderAssetPath =
|
||||
sandboxRoot / "engine" / "assets" / "builtin" / "shaders" / "shadow-caster.shader";
|
||||
|
||||
const fs::path previousPath = fs::current_path();
|
||||
fs::remove_all(sandboxRoot);
|
||||
fs::create_directories(shaderDir);
|
||||
fs::create_directories(builtinShaderAssetPath.parent_path());
|
||||
|
||||
auto writeBuiltinShader = [&](const char* cullMode, const char* marker) {
|
||||
WriteTextFile(
|
||||
builtinShaderAssetPath,
|
||||
std::string(R"(Shader "Builtin Shadow Caster"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
Pass
|
||||
{
|
||||
Name "ShadowCaster"
|
||||
Tags { "LightMode" = "ShadowCaster" }
|
||||
Cull )") +
|
||||
cullMode +
|
||||
R"(
|
||||
HLSLPROGRAM
|
||||
#pragma vertex MainVS
|
||||
#pragma fragment MainPS
|
||||
float4 MainVS() : SV_POSITION { return 0; }
|
||||
float4 MainPS() : SV_TARGET { return )" +
|
||||
marker +
|
||||
R"(; }
|
||||
ENDHLSL
|
||||
}
|
||||
}
|
||||
}
|
||||
)");
|
||||
};
|
||||
|
||||
writeBuiltinShader("Back", "float4(1.0, 0.0, 0.0, 1.0)");
|
||||
WriteTextFile(
|
||||
mainShaderPath,
|
||||
R"(Shader "Builtin UsePass Dependency Shader"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
UsePass "Builtin Shadow Caster/ShadowCaster"
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
||||
fs::current_path(projectRoot);
|
||||
|
||||
ShaderLoader dependencyLoader;
|
||||
Array<String> firstDependencies;
|
||||
ASSERT_TRUE(dependencyLoader.CollectSourceDependencies(mainShaderPath.string().c_str(), firstDependencies));
|
||||
ASSERT_EQ(firstDependencies.Size(), 1u);
|
||||
EXPECT_EQ(
|
||||
fs::path(firstDependencies[0].CStr()).lexically_normal(),
|
||||
builtinShaderAssetPath.lexically_normal());
|
||||
|
||||
AssetDatabase database;
|
||||
database.Initialize(projectRoot.string().c_str());
|
||||
|
||||
AssetDatabase::ResolvedAsset firstResolve;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/Shaders/main.shader", ResourceType::Shader, firstResolve));
|
||||
ASSERT_TRUE(firstResolve.artifactReady);
|
||||
|
||||
ShaderLoader loader;
|
||||
LoadResult firstLoad = loader.Load(firstResolve.artifactMainPath.CStr());
|
||||
ASSERT_TRUE(firstLoad);
|
||||
auto* firstShader = static_cast<Shader*>(firstLoad.resource);
|
||||
ASSERT_NE(firstShader, nullptr);
|
||||
const ShaderPass* firstPass = firstShader->FindPass("ShadowCaster");
|
||||
ASSERT_NE(firstPass, nullptr);
|
||||
EXPECT_EQ(firstPass->fixedFunctionState.cullMode, MaterialCullMode::Back);
|
||||
const ShaderStageVariant* firstFragmentVariant =
|
||||
firstShader->FindVariant("ShadowCaster", ShaderType::Fragment, ShaderBackend::D3D12);
|
||||
ASSERT_NE(firstFragmentVariant, nullptr);
|
||||
EXPECT_NE(
|
||||
std::string(firstFragmentVariant->sourceCode.CStr()).find("float4(1.0, 0.0, 0.0, 1.0)"),
|
||||
std::string::npos);
|
||||
delete firstShader;
|
||||
|
||||
const String firstArtifactPath = firstResolve.artifactMainPath;
|
||||
database.Shutdown();
|
||||
|
||||
std::this_thread::sleep_for(50ms);
|
||||
writeBuiltinShader("Front", "float4(0.0, 1.0, 0.0, 1.0)");
|
||||
|
||||
database.Initialize(projectRoot.string().c_str());
|
||||
AssetDatabase::ResolvedAsset secondResolve;
|
||||
ASSERT_TRUE(database.EnsureArtifact("Assets/Shaders/main.shader", ResourceType::Shader, secondResolve));
|
||||
ASSERT_TRUE(secondResolve.artifactReady);
|
||||
EXPECT_NE(secondResolve.artifactMainPath, firstArtifactPath);
|
||||
|
||||
LoadResult secondLoad = loader.Load(secondResolve.artifactMainPath.CStr());
|
||||
ASSERT_TRUE(secondLoad);
|
||||
auto* secondShader = static_cast<Shader*>(secondLoad.resource);
|
||||
ASSERT_NE(secondShader, nullptr);
|
||||
const ShaderPass* secondPass = secondShader->FindPass("ShadowCaster");
|
||||
ASSERT_NE(secondPass, nullptr);
|
||||
EXPECT_EQ(secondPass->fixedFunctionState.cullMode, MaterialCullMode::Front);
|
||||
const ShaderStageVariant* secondFragmentVariant =
|
||||
secondShader->FindVariant("ShadowCaster", ShaderType::Fragment, ShaderBackend::D3D12);
|
||||
ASSERT_NE(secondFragmentVariant, nullptr);
|
||||
EXPECT_NE(
|
||||
std::string(secondFragmentVariant->sourceCode.CStr()).find("float4(0.0, 1.0, 0.0, 1.0)"),
|
||||
std::string::npos);
|
||||
delete secondShader;
|
||||
|
||||
database.Shutdown();
|
||||
fs::current_path(previousPath);
|
||||
fs::remove_all(sandboxRoot);
|
||||
}
|
||||
|
||||
TEST(ShaderLoader, LoadShaderAuthoringRejectsCyclicProjectUsePass) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
const fs::path shaderRoot = fs::temp_directory_path() / "xc_shader_authoring_usepass_cycle";
|
||||
const fs::path shaderAPath = shaderRoot / "a.shader";
|
||||
const fs::path shaderBPath = shaderRoot / "b.shader";
|
||||
|
||||
fs::remove_all(shaderRoot);
|
||||
fs::create_directories(shaderRoot);
|
||||
|
||||
WriteTextFile(
|
||||
shaderAPath,
|
||||
R"(Shader "Cycle Shader A"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
UsePass "Cycle Shader B/PassB"
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
||||
WriteTextFile(
|
||||
shaderBPath,
|
||||
R"(Shader "Cycle Shader B"
|
||||
{
|
||||
SubShader
|
||||
{
|
||||
UsePass "Cycle Shader A/PassA"
|
||||
}
|
||||
}
|
||||
)");
|
||||
|
||||
ShaderLoader loader;
|
||||
LoadResult result = loader.Load(shaderAPath.string().c_str());
|
||||
EXPECT_FALSE(result.success);
|
||||
EXPECT_EQ(result.resource, nullptr);
|
||||
EXPECT_NE(
|
||||
std::string(result.errorMessage.CStr()).find("cyclic shader reference"),
|
||||
std::string::npos);
|
||||
|
||||
fs::remove_all(shaderRoot);
|
||||
}
|
||||
|
||||
TEST(ShaderLoader, LoadShaderAuthoringRejectsLegacyBackendPragma) {
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
|
||||
Reference in New Issue
Block a user