#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace { class ScopedCurrentPath { public: explicit ScopedCurrentPath(const std::filesystem::path& path) : m_previousPath(std::filesystem::current_path()) { std::filesystem::current_path(path); } ScopedCurrentPath(const ScopedCurrentPath&) = delete; ScopedCurrentPath& operator=(const ScopedCurrentPath&) = delete; ~ScopedCurrentPath() { std::error_code ec; std::filesystem::current_path(m_previousPath, ec); } private: std::filesystem::path m_previousPath; }; bool ContainsPathSuffix( const XCEngine::Containers::Array& paths, const char* suffix) { for (size_t index = 0; index < paths.Size(); ++index) { const std::string path = paths[index].CStr(); if (path.ends_with(suffix)) { return true; } } return false; } bool WriteTextFile(const std::filesystem::path& path, const char* text) { std::filesystem::create_directories(path.parent_path()); std::ofstream stream(path, std::ios::binary); if (!stream) { return false; } stream << text; return true; } std::vector SplitTabFields(const std::string& line) { std::vector fields; size_t fieldStart = 0; while (fieldStart <= line.size()) { const size_t tabPos = line.find('\t', fieldStart); if (tabPos == std::string::npos) { fields.push_back(line.substr(fieldStart)); break; } fields.push_back(line.substr(fieldStart, tabPos - fieldStart)); fieldStart = tabPos + 1; } return fields; } bool LoadSourceDbShaderRecord( const std::filesystem::path& projectRoot, const char* relativePath, std::vector& outFields) { std::ifstream input(projectRoot / "Library/assets.db"); if (!input.is_open()) { std::cerr << "failed to open source asset db\n"; return false; } std::string line; while (std::getline(input, line)) { if (line.empty() || line[0] == '#') { continue; } std::vector fields = SplitTabFields(line); if (fields.size() >= 10 && fields[1] == relativePath) { outFields = std::move(fields); return true; } } std::cerr << "source asset db did not contain record: " << relativePath << "\n"; return false; } XCEngine::Core::uint64 GetFileSizeValue(const std::filesystem::path& path) { std::error_code ec; const auto size = std::filesystem::file_size(path, ec); return ec ? 0 : static_cast(size); } XCEngine::Core::uint64 GetFileWriteTimeValue(const std::filesystem::path& path) { std::error_code ec; const auto writeTime = std::filesystem::last_write_time(path, ec); if (ec) { return 0; } return static_cast(writeTime.time_since_epoch().count()); } bool WriteMinimalCurrentShaderArtifact(const std::filesystem::path& artifactPath) { XCEngine::Resources::ShaderArtifactFileHeader fileHeader = {}; XCEngine::Resources::ShaderArtifactHeader shaderHeader = {}; XCEngine::Containers::Array payload; payload.Resize(sizeof(fileHeader) + sizeof(shaderHeader)); std::memcpy(payload.Data(), &fileHeader, sizeof(fileHeader)); std::memcpy( payload.Data() + sizeof(fileHeader), &shaderHeader, sizeof(shaderHeader)); XCEngine::Resources::ArtifactContainerEntry entry = {}; entry.name = "main"; entry.resourceType = XCEngine::Resources::ResourceType::Shader; entry.localID = XCEngine::Resources::kMainAssetLocalID; entry.payload = std::move(payload); XCEngine::Resources::ArtifactContainerWriter writer; writer.AddEntry(std::move(entry)); XCEngine::Containers::String error; if (!writer.WriteToFile(artifactPath.generic_string().c_str(), &error)) { std::cerr << "failed to write minimal shader artifact: " << error.CStr() << "\n"; return false; } return true; } bool WriteShaderArtifactDbRecord( const std::filesystem::path& projectRoot, const std::vector& sourceFields, const std::filesystem::path& dependencyPath, XCEngine::Core::uint64 dependencyFileSize, XCEngine::Core::uint64 dependencyWriteTime, const std::string& artifactKey, const std::string& mainArtifactPath) { if (sourceFields.size() < 10) { return false; } const std::filesystem::path artifactDbPath = projectRoot / "Library/artifacts.db"; std::filesystem::create_directories(artifactDbPath.parent_path()); std::ofstream output(artifactDbPath, std::ios::out | std::ios::trunc); if (!output.is_open()) { std::cerr << "failed to write artifact db: " << artifactDbPath << "\n"; return false; } output << "# schema=2\n"; output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\tstorageKind\tmainEntryName\t(depPath\tdepHash\tdepSize\tdepWriteTime)...\n"; output << artifactKey << '\t' << sourceFields[0] << '\t' << "ShaderImporter" << '\t' << "8" << '\t' << static_cast(XCEngine::Resources::ResourceType::Shader) << '\t' << std::filesystem::path(mainArtifactPath).parent_path().generic_string() << '\t' << mainArtifactPath << '\t' << sourceFields[7] << '\t' << sourceFields[6] << '\t' << sourceFields[8] << '\t' << sourceFields[9] << '\t' << XCEngine::Resources::kMainAssetLocalID << '\t' << static_cast( XCEngine::Resources::ArtifactStorageKind::SingleFileContainer) << '\t' << "main" << '\t' << dependencyPath.lexically_normal().generic_string() << '\t' << "dependency-hash-not-used-by-currentness-check" << '\t' << dependencyFileSize << '\t' << dependencyWriteTime << '\n'; return static_cast(output); } bool CollectDependencies( const std::filesystem::path& shaderPath, XCEngine::Containers::Array& dependencies) { XCEngine::Resources::ShaderLoader loader; return loader.CollectSourceDependencies(shaderPath.generic_string().c_str(), dependencies); } bool CollectDependencies( const std::filesystem::path& shaderPath, XCEngine::Containers::Array& dependencies, XCEngine::Containers::String& error) { XCEngine::Resources::ShaderLoader loader; return loader.CollectSourceDependencies(shaderPath.generic_string().c_str(), dependencies, &error); } std::string NarrowWideString(const std::wstring& value) { std::string result; result.reserve(value.size()); for (wchar_t ch : value) { result.push_back(static_cast(ch)); } return result; } bool RequireDependencySuffixes( const XCEngine::Containers::Array& dependencies, const std::vector& suffixes) { for (const char* suffix : suffixes) { if (ContainsPathSuffix(dependencies, suffix)) { continue; } std::cerr << "missing dependency suffix: " << suffix << "\n"; for (const auto& dependency : dependencies) { std::cerr << "dependency: " << dependency.CStr() << "\n"; } return false; } return true; } bool RequireNoDependencyPrefix( const XCEngine::Containers::Array& dependencies, const std::filesystem::path& prefixPath) { const std::string prefix = prefixPath.lexically_normal().generic_string(); for (const auto& dependency : dependencies) { const std::string path = std::filesystem::path(dependency.CStr()) .lexically_normal() .generic_string(); if (path.rfind(prefix, 0) == 0) { std::cerr << "dependency unexpectedly resolved under shadow package root: " << path << "\n"; return false; } } return true; } bool RequireDependencySuffixUnderPrefix( const XCEngine::Containers::Array& dependencies, const char* suffix, const std::filesystem::path& prefixPath) { const std::string prefix = prefixPath.lexically_normal().generic_string(); bool sawSuffix = false; for (const auto& dependency : dependencies) { const std::string path = std::filesystem::path(dependency.CStr()) .lexically_normal() .generic_string(); if (!path.ends_with(suffix)) { continue; } sawSuffix = true; if (path.rfind(prefix, 0) == 0) { return true; } std::cerr << "dependency suffix resolved outside expected prefix: " << path << " prefix=" << prefix << "\n"; } if (!sawSuffix) { std::cerr << "missing dependency suffix under prefix: " << suffix << "\n"; } for (const auto& dependency : dependencies) { std::cerr << "dependency: " << dependency.CStr() << "\n"; } return false; } bool CompileD3D12ShaderVariant( const std::filesystem::path& shaderPath, const XCEngine::Resources::ShaderPass& pass, const XCEngine::Resources::ShaderStageVariant& variant) { XCEngine::RHI::ShaderCompileDesc compileDesc = {}; XCEngine::Rendering::Internal::ApplyShaderStageVariant( shaderPath.generic_string().c_str(), pass, XCEngine::Resources::ShaderBackend::D3D12, variant, compileDesc); std::vector macroNames; std::vector macroDefinitions; std::vector macros; macroNames.reserve(compileDesc.macros.size()); macroDefinitions.reserve(compileDesc.macros.size()); macros.reserve(compileDesc.macros.size() + 1u); for (const XCEngine::RHI::ShaderCompileMacro& macro : compileDesc.macros) { macroNames.push_back(NarrowWideString(macro.name)); macroDefinitions.push_back(NarrowWideString(macro.definition)); } for (size_t macroIndex = 0; macroIndex < macroNames.size(); ++macroIndex) { D3D_SHADER_MACRO d3dMacro = {}; d3dMacro.Name = macroNames[macroIndex].c_str(); d3dMacro.Definition = macroDefinitions[macroIndex].empty() ? "1" : macroDefinitions[macroIndex].c_str(); macros.push_back(d3dMacro); } macros.push_back({ nullptr, nullptr }); const std::string entryPoint = NarrowWideString(compileDesc.entryPoint); const std::string profile = NarrowWideString(compileDesc.profile); XCEngine::RHI::D3D12Shader compiledShader; if (!compiledShader.Compile( compileDesc.source.data(), compileDesc.source.size(), compileDesc.fileName.empty() ? nullptr : compileDesc.fileName.c_str(), macros.data(), entryPoint.empty() ? nullptr : entryPoint.c_str(), profile.empty() ? nullptr : profile.c_str())) { std::cerr << "D3D12 compile failed for entry=" << entryPoint << " profile=" << profile << "\n"; return false; } if (compiledShader.GetBytecodeSize() == 0) { std::cerr << "D3D12 compile produced empty bytecode for entry=" << entryPoint << "\n"; return false; } return true; } bool RequirePackageShaderLoadAndD3D12Compile(const std::filesystem::path& shaderPath) { XCEngine::Resources::ShaderLoader loader; XCEngine::Resources::LoadResult result = loader.Load(shaderPath.generic_string().c_str()); if (!result || result.resource == nullptr) { std::cerr << "failed to load package compile shader: " << result.errorMessage.CStr() << "\n"; return false; } std::unique_ptr shader( static_cast(result.resource)); const XCEngine::Resources::ShaderPass* pass = shader->FindPass("PackageCompile"); if (pass == nullptr) { std::cerr << "loaded package compile shader is missing PackageCompile pass\n"; return false; } const XCEngine::Resources::ShaderStageVariant* vertexVariant = shader->FindVariant( "PackageCompile", XCEngine::Resources::ShaderType::Vertex, XCEngine::Resources::ShaderBackend::Generic); const XCEngine::Resources::ShaderStageVariant* fragmentVariant = shader->FindVariant( "PackageCompile", XCEngine::Resources::ShaderType::Fragment, XCEngine::Resources::ShaderBackend::Generic); if (vertexVariant == nullptr || fragmentVariant == nullptr) { std::cerr << "loaded package compile shader is missing graphics variants\n"; return false; } const std::string vertexSource = vertexVariant->sourceCode.CStr(); if (vertexSource.find("Packages/") != std::string::npos || vertexSource.find("TransformObjectToHClip") == std::string::npos || vertexSource.find("XC_UNIVERSAL_CORE_INCLUDED") == std::string::npos) { std::cerr << "package include was not expanded into compile-ready source\n"; return false; } return CompileD3D12ShaderVariant(shaderPath, *pass, *vertexVariant) && CompileD3D12ShaderVariant(shaderPath, *pass, *fragmentVariant); } bool RequirePackageDependencyCurrentPathFallback( const std::filesystem::path& projectRoot) { std::error_code cleanupError; std::filesystem::remove_all(projectRoot, cleanupError); const char* includePath = "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/CurrentPathFallbackOnly_20260425.hlsl"; const char* resolvedSuffix = "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/CurrentPathFallbackOnly_20260425.hlsl"; const std::filesystem::path shaderPath = projectRoot / "current_path_fallback.shader"; const std::filesystem::path dependencyPath = projectRoot / resolvedSuffix; const std::string shaderSource = std::string("Shader \"Tests/CurrentPathFallback\"\n") + "{\n" " HLSLINCLUDE\n" " #include \"" + includePath + "\"\n" " ENDHLSL\n" " SubShader\n" " {\n" " }\n" "}\n"; if (!WriteTextFile(shaderPath, shaderSource.c_str()) || !WriteTextFile(dependencyPath, "float4 CurrentPathFallbackOnlyValue() { return 1.0f.xxxx; }\n")) { std::cerr << "failed to prepare current-path package fallback fixtures\n"; return false; } XCEngine::Containers::Array dependencies; XCEngine::Containers::String dependencyError; { ScopedCurrentPath scopedCurrentPath(projectRoot); if (!CollectDependencies(shaderPath, dependencies, dependencyError)) { std::cerr << "current-path package fallback dependency collection failed: " << dependencyError.CStr() << "\n"; return false; } } return RequireDependencySuffixUnderPrefix( dependencies, resolvedSuffix, projectRoot); } bool RequireShaderArtifactLoads( const std::filesystem::path& projectRoot, const XCEngine::Containers::String& artifactMainPath, const char* label) { if (artifactMainPath.Empty()) { std::cerr << label << " artifact path is empty\n"; return false; } const std::filesystem::path absoluteArtifactPath = projectRoot / artifactMainPath.CStr(); if (!std::filesystem::exists(absoluteArtifactPath)) { std::cerr << label << " artifact file does not exist: " << absoluteArtifactPath << "\n"; return false; } XCEngine::Resources::ShaderLoader loader; XCEngine::Resources::LoadResult result = loader.Load(absoluteArtifactPath.generic_string().c_str()); if (!result || result.resource == nullptr) { std::cerr << label << " artifact failed to load: " << result.errorMessage.CStr() << "\n"; return false; } delete static_cast(result.resource); return true; } bool RequireAssetDatabaseImportFailureDiagnostic(const std::filesystem::path& projectRoot) { const std::filesystem::path shaderPath = projectRoot / "Assets/MissingPackageInclude.shader"; const char* shaderSource = R"(Shader "Tests/AssetDatabaseMissingPackageInclude" { SubShader { Pass { Name "AssetDatabaseMissingPackageInclude" HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/MissingAssetDatabaseFile.hlsl" float4 MainVS(float3 positionOS : POSITION) : SV_POSITION { return float4(positionOS, 1.0f); } float4 MainPS() : SV_TARGET { return 1.0f.xxxx; } ENDHLSL } } } )"; if (!WriteTextFile(shaderPath, shaderSource)) { std::cerr << "failed to write AssetDatabase missing include shader fixture: " << shaderPath << "\n"; return false; } XCEngine::Resources::AssetDatabase assetDatabase; assetDatabase.Initialize(projectRoot.generic_string().c_str()); XCEngine::Resources::AssetDatabase::ResolvedAsset resolvedAsset; XCEngine::Resources::AssetDatabase::MaintenanceStats stats; const bool imported = assetDatabase.ReimportAsset( "Assets/MissingPackageInclude.shader", resolvedAsset, &stats); const std::string errorText = assetDatabase.GetLastErrorMessage().CStr(); assetDatabase.Shutdown(); if (imported) { std::cerr << "AssetDatabase unexpectedly imported shader with missing package include\n"; return false; } if (errorText.find( "requested=Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/MissingAssetDatabaseFile.hlsl") == std::string::npos || errorText.find( "normalized=Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/MissingAssetDatabaseFile.hlsl") == std::string::npos || errorText.find("source=") == std::string::npos || errorText.find("normalizedCandidate=") == std::string::npos || errorText.find("searchedAnchors=[sourceRoot=") == std::string::npos || errorText.find("includeChain=") == std::string::npos || errorText.find("builtinPackageRoot=engine/assets/builtin/shaders/Packages") == std::string::npos) { std::cerr << "AssetDatabase missing package include diagnostic is not actionable: " << errorText << "\n"; return false; } return true; } bool RequirePackageEditProducesNewShaderArtifact( const std::filesystem::path& projectRoot) { std::error_code cleanupError; std::filesystem::remove_all(projectRoot, cleanupError); std::filesystem::create_directories(projectRoot / "Assets"); ScopedCurrentPath scopedCurrentPath(projectRoot); const char* relativeShaderPath = "Assets/PackageArtifactEdit.shader"; const std::filesystem::path shaderPath = projectRoot / relativeShaderPath; const std::filesystem::path dependencyPath = projectRoot / "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/ArtifactEditTest.hlsl"; const char* shaderSource = R"(Shader "Tests/PackageArtifactEdit" { HLSLINCLUDE #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/ArtifactEditTest.hlsl" ENDHLSL SubShader { } } )"; if (!WriteTextFile(shaderPath, shaderSource) || !WriteTextFile(dependencyPath, "float4 PackageArtifactEditValue() { return 1.0f.xxxx; }\n")) { std::cerr << "failed to prepare package edit artifact fixtures\n"; return false; } XCEngine::Resources::AssetDatabase assetDatabase; assetDatabase.Initialize(projectRoot.generic_string().c_str()); XCEngine::Resources::AssetDatabase::ResolvedAsset firstArtifact; if (!assetDatabase.EnsureArtifact( relativeShaderPath, XCEngine::Resources::ResourceType::Shader, firstArtifact)) { std::cerr << "initial package-edit shader import failed: " << assetDatabase.GetLastErrorMessage().CStr() << "\n"; assetDatabase.Shutdown(); return false; } if (!firstArtifact.imported || !RequireShaderArtifactLoads(projectRoot, firstArtifact.artifactMainPath, "initial package-edit shader")) { std::cerr << "initial package-edit shader did not produce a valid new artifact\n"; assetDatabase.Shutdown(); return false; } const std::string firstArtifactPath = firstArtifact.artifactMainPath.CStr(); if (!WriteTextFile( dependencyPath, "float4 PackageArtifactEditValue() { return float4(0.25f, 0.5f, 0.75f, 1.0f); }\n" "float4 PackageArtifactEditValueAfterEdit() { return 2.0f.xxxx; }\n")) { std::cerr << "failed to edit package dependency fixture\n"; assetDatabase.Shutdown(); return false; } XCEngine::Resources::AssetDatabase::ResolvedAsset secondArtifact; if (!assetDatabase.EnsureArtifact( relativeShaderPath, XCEngine::Resources::ResourceType::Shader, secondArtifact)) { std::cerr << "package-edit shader reimport failed after dependency edit: " << assetDatabase.GetLastErrorMessage().CStr() << "\n"; assetDatabase.Shutdown(); return false; } const std::string secondArtifactPath = secondArtifact.artifactMainPath.CStr(); if (!secondArtifact.imported || secondArtifactPath.empty() || secondArtifactPath == firstArtifactPath || !RequireShaderArtifactLoads(projectRoot, secondArtifact.artifactMainPath, "edited package shader")) { std::cerr << "package edit did not produce a distinct valid shader artifact; first=" << firstArtifactPath << " second=" << secondArtifactPath << "\n"; assetDatabase.Shutdown(); return false; } XCEngine::Resources::AssetDatabase::ResolvedAsset thirdArtifact; if (!assetDatabase.EnsureArtifact( relativeShaderPath, XCEngine::Resources::ResourceType::Shader, thirdArtifact)) { std::cerr << "package-edit shader unchanged ensure failed: " << assetDatabase.GetLastErrorMessage().CStr() << "\n"; assetDatabase.Shutdown(); return false; } const std::string thirdArtifactPath = thirdArtifact.artifactMainPath.CStr(); if (thirdArtifact.imported || thirdArtifactPath != secondArtifactPath) { std::cerr << "unchanged package dependency did not reuse current shader artifact; second=" << secondArtifactPath << " third=" << thirdArtifactPath << "\n"; assetDatabase.Shutdown(); return false; } assetDatabase.Shutdown(); return true; } bool RequirePackageDependencyStaleRecordTriggersShaderReimport( const std::filesystem::path& projectRoot) { std::error_code cleanupError; std::filesystem::remove_all(projectRoot, cleanupError); const char* relativeShaderPath = "Assets/StalePackageDependency.shader"; const std::filesystem::path shaderPath = projectRoot / relativeShaderPath; const std::filesystem::path dependencyPath = projectRoot / "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/StaleArtifactDependency.hlsl"; const std::string artifactKey = "0123456789abcdef0123456789abcdef"; const std::string mainArtifactPath = "Library/Artifacts/01/" + artifactKey + ".xcshader"; const char* shaderSource = R"(Shader "Tests/StalePackageDependency" { SubShader { Pass { Name "StalePackageDependency" HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/MissingAfterInvalidation.hlsl" float4 MainVS(float3 positionOS : POSITION) : SV_POSITION { return float4(positionOS, 1.0f); } float4 MainPS() : SV_TARGET { return 1.0f.xxxx; } ENDHLSL } } } )"; if (!WriteTextFile(shaderPath, shaderSource) || !WriteTextFile(dependencyPath, "float4 StaleArtifactDependencyValue() { return 1.0f.xxxx; }\n")) { std::cerr << "failed to prepare stale package dependency fixtures\n"; return false; } { XCEngine::Resources::AssetDatabase assetDatabase; assetDatabase.Initialize(projectRoot.generic_string().c_str()); assetDatabase.Refresh(); assetDatabase.Shutdown(); } std::vector sourceFields; if (!LoadSourceDbShaderRecord(projectRoot, relativeShaderPath, sourceFields)) { return false; } if (!WriteMinimalCurrentShaderArtifact(projectRoot / mainArtifactPath)) { std::cerr << "failed to write stale package dependency artifact fixture\n"; return false; } const XCEngine::Core::uint64 currentDependencySize = GetFileSizeValue(dependencyPath); const XCEngine::Core::uint64 currentDependencyWriteTime = GetFileWriteTimeValue(dependencyPath); if (!WriteShaderArtifactDbRecord( projectRoot, sourceFields, dependencyPath, currentDependencySize, currentDependencyWriteTime, artifactKey, mainArtifactPath)) { return false; } { XCEngine::Resources::AssetDatabase assetDatabase; assetDatabase.Initialize(projectRoot.generic_string().c_str()); XCEngine::Resources::AssetDatabase::ResolvedAsset resolvedAsset; if (!assetDatabase.EnsureArtifact( relativeShaderPath, XCEngine::Resources::ResourceType::Shader, resolvedAsset)) { std::cerr << "current package dependency record unexpectedly triggered import: " << assetDatabase.GetLastErrorMessage().CStr() << "\n"; assetDatabase.Shutdown(); return false; } if (resolvedAsset.imported || resolvedAsset.artifactMainPath.CStr()[0] == '\0') { std::cerr << "current package dependency record did not use existing artifact\n"; assetDatabase.Shutdown(); return false; } assetDatabase.Shutdown(); } const XCEngine::Core::uint64 staleWriteTime = currentDependencyWriteTime == 1 ? 2 : 1; if (!WriteShaderArtifactDbRecord( projectRoot, sourceFields, dependencyPath, currentDependencySize, staleWriteTime, artifactKey, mainArtifactPath)) { return false; } { XCEngine::Resources::AssetDatabase assetDatabase; assetDatabase.Initialize(projectRoot.generic_string().c_str()); XCEngine::Resources::AssetDatabase::ResolvedAsset resolvedAsset; const bool ensured = assetDatabase.EnsureArtifact( relativeShaderPath, XCEngine::Resources::ResourceType::Shader, resolvedAsset); const std::string errorText = assetDatabase.GetLastErrorMessage().CStr(); assetDatabase.Shutdown(); if (ensured) { std::cerr << "stale package dependency record did not trigger shader reimport\n"; return false; } if (errorText.find( "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/MissingAfterInvalidation.hlsl") == std::string::npos) { std::cerr << "stale package dependency reimport did not reach shader loader diagnostic: " << errorText << "\n"; return false; } } return true; } bool RequireIncludeFailureDiagnostic( const char* label, const XCEngine::Containers::String& error, const std::string& requestedPath) { const std::string errorText = error.CStr(); if (errorText.find("requested=" + requestedPath) == std::string::npos || errorText.find("normalized=") == std::string::npos || errorText.find("source=") == std::string::npos || errorText.find("normalizedCandidate=") == std::string::npos || errorText.find("searchedAnchors=[sourceRoot=") == std::string::npos || errorText.find("resourceRoot=") == std::string::npos || errorText.find("currentPath=") == std::string::npos || errorText.find("includeChain=") == std::string::npos || errorText.find("builtinPackageRoot=engine/assets/builtin/shaders/Packages") == std::string::npos) { std::cerr << label << " diagnostic is not actionable: " << errorText << "\n"; return false; } return true; } } // namespace int main() { const std::filesystem::path tempRoot = std::filesystem::temp_directory_path() / "xcengine_shader_dependency_tests"; const std::filesystem::path shaderPath = tempRoot / "package_include.shader"; const char* shaderSource = R"(Shader "Tests/PackageInclude" { SubShader { Pass { Name "PackageInclude" HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/Core.hlsl" float4 MainVS(float3 positionOS : POSITION) : SV_POSITION { return TransformObjectToHClip(positionOS); } float4 MainPS() : SV_TARGET { return 1.0f.xxxx; } ENDHLSL } } } )"; if (!WriteTextFile(shaderPath, shaderSource)) { std::cerr << "failed to write shader fixture: " << shaderPath << "\n"; return 1; } XCEngine::Containers::Array dependencies; if (!CollectDependencies(shaderPath, dependencies)) { std::cerr << "CollectSourceDependencies failed\n"; return 1; } if (!RequireDependencySuffixes( dependencies, { "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/Core.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.core/ShaderLibrary/ShaderVariables.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl", })) { return 1; } const std::filesystem::path originalCurrentPath = std::filesystem::current_path(); const std::filesystem::path shadowPackageRoot = tempRoot / "engine/assets/builtin/shaders/Packages"; const std::filesystem::path shadowCorePath = shadowPackageRoot / "com.xcengine.render-pipelines.universal/ShaderLibrary/Core.hlsl"; if (!WriteTextFile(shadowCorePath, "float4 ShadowRootSelected() { return 1.0f.xxxx; }\n")) { std::cerr << "failed to write shadow package include fixture: " << shadowCorePath << "\n"; return 1; } std::filesystem::current_path(tempRoot); dependencies.Clear(); if (!CollectDependencies(shaderPath, dependencies)) { std::filesystem::current_path(originalCurrentPath); std::cerr << "CollectSourceDependencies failed while current directory contained a shadow package root\n"; return 1; } std::filesystem::current_path(originalCurrentPath); if (!RequireDependencySuffixes( dependencies, { "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/Core.hlsl", }) || !RequireNoDependencyPrefix(dependencies, shadowPackageRoot)) { return 1; } const std::filesystem::path currentPathFallbackProjectRoot = tempRoot / "current_path_fallback_project"; if (!RequirePackageDependencyCurrentPathFallback(currentPathFallbackProjectRoot)) { return 1; } const std::filesystem::path packageCompileShaderPath = tempRoot / "package_compile.shader"; const char* packageCompileShaderSource = R"(Shader "Tests/PackageCompile" { SubShader { Pass { Name "PackageCompile" HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/Core.hlsl" struct Attributes { float3 positionOS : POSITION; }; struct Varyings { float4 positionCS : SV_POSITION; }; Varyings MainVS(Attributes input) { Varyings output; output.positionCS = TransformObjectToHClip(input.positionOS); return output; } float4 MainPS(Varyings input) : SV_TARGET { return input.positionCS; } ENDHLSL } } } )"; if (!WriteTextFile(packageCompileShaderPath, packageCompileShaderSource)) { std::cerr << "failed to write package compile shader fixture: " << packageCompileShaderPath << "\n"; return 1; } if (!RequirePackageShaderLoadAndD3D12Compile(packageCompileShaderPath)) { return 1; } const std::filesystem::path backslashPackageIncludeShaderPath = tempRoot / "backslash_package_include.shader"; const char* backslashPackageIncludeShaderSource = R"(Shader "Tests/BackslashPackageInclude" { SubShader { Pass { Name "BackslashPackageInclude" HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS #include "Packages\com.xcengine.render-pipelines.universal\ShaderLibrary\Core.hlsl" float4 MainVS(float3 positionOS : POSITION) : SV_POSITION { return TransformObjectToHClip(positionOS); } float4 MainPS() : SV_TARGET { return 1.0f.xxxx; } ENDHLSL } } } )"; if (!WriteTextFile(backslashPackageIncludeShaderPath, backslashPackageIncludeShaderSource)) { std::cerr << "failed to write backslash package include shader fixture: " << backslashPackageIncludeShaderPath << "\n"; return 1; } dependencies.Clear(); if (!CollectDependencies(backslashPackageIncludeShaderPath, dependencies)) { std::cerr << "CollectSourceDependencies failed for backslash package include\n"; return 1; } if (!RequireDependencySuffixes( dependencies, { "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/Core.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.core/ShaderLibrary/ShaderVariables.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl", })) { return 1; } const std::filesystem::path allPublicIncludesShaderPath = tempRoot / "all_public_package_includes.shader"; const char* allPublicIncludesShaderSource = R"(Shader "Tests/AllPublicPackageIncludes" { SubShader { Pass { Name "AllPublicPackageIncludes" HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS #include "Packages/com.xcengine.render-pipelines.core/ShaderLibrary/ShaderVariables.hlsl" #include "Packages/com.xcengine.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl" #include "Packages/com.xcengine.render-pipelines.core/ShaderLibrary/TextureSampling.hlsl" #include "Packages/com.xcengine.render-pipelines.core/ShaderLibrary/Color.hlsl" #include "Packages/com.xcengine.render-pipelines.core/ShaderLibrary/Packing.hlsl" #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/Core.hlsl" #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/Input.hlsl" #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl" #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/ShaderPass.hlsl" #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl" float4 MainVS(float3 positionOS : POSITION) : SV_POSITION { return TransformObjectToHClip(positionOS); } float4 MainPS() : SV_TARGET { return XcLinearToSrgb(1.0f.xxx).xyzz; } ENDHLSL } } } )"; if (!WriteTextFile(allPublicIncludesShaderPath, allPublicIncludesShaderSource)) { std::cerr << "failed to write all public package include shader fixture: " << allPublicIncludesShaderPath << "\n"; return 1; } dependencies.Clear(); if (!CollectDependencies(allPublicIncludesShaderPath, dependencies)) { std::cerr << "CollectSourceDependencies failed for all public package includes\n"; return 1; } if (!RequireDependencySuffixes( dependencies, { "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.core/ShaderLibrary/ShaderVariables.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.core/ShaderLibrary/SpaceTransforms.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.core/ShaderLibrary/TextureSampling.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.core/ShaderLibrary/Color.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.core/ShaderLibrary/Packing.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/Core.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/Input.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/SurfaceInput.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/ShaderPass.hlsl", "engine/assets/builtin/shaders/Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/RealtimeLights.hlsl", })) { return 1; } const std::filesystem::path missingIncludeShaderPath = tempRoot / "missing_package_include.shader"; const char* missingIncludeShaderSource = R"(Shader "Tests/MissingPackageInclude" { SubShader { Pass { Name "MissingPackageInclude" HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS #include "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/MissingFile.hlsl" float4 MainVS(float3 positionOS : POSITION) : SV_POSITION { return float4(positionOS, 1.0f); } float4 MainPS() : SV_TARGET { return 1.0f.xxxx; } ENDHLSL } } } )"; if (!WriteTextFile(missingIncludeShaderPath, missingIncludeShaderSource)) { std::cerr << "failed to write missing include shader fixture: " << missingIncludeShaderPath << "\n"; return 1; } dependencies.Clear(); XCEngine::Containers::String dependencyError; if (CollectDependencies(missingIncludeShaderPath, dependencies, dependencyError)) { std::cerr << "CollectSourceDependencies unexpectedly accepted a missing package include\n"; return 1; } if (!RequireIncludeFailureDiagnostic( "missing package include", dependencyError, "Packages/com.xcengine.render-pipelines.universal/ShaderLibrary/MissingFile.hlsl")) { return 1; } const std::filesystem::path escapedPackageShaderPath = tempRoot / "escaped_package_include.shader"; const char* escapedPackageShaderSource = R"(Shader "Tests/EscapedPackageInclude" { SubShader { Pass { Name "EscapedPackageInclude" HLSLPROGRAM #pragma vertex MainVS #pragma fragment MainPS #include "Packages/../forward-lit.shader" float4 MainVS(float3 positionOS : POSITION) : SV_POSITION { return float4(positionOS, 1.0f); } float4 MainPS() : SV_TARGET { return 1.0f.xxxx; } ENDHLSL } } } )"; if (!WriteTextFile(escapedPackageShaderPath, escapedPackageShaderSource)) { std::cerr << "failed to write escaped package include shader fixture: " << escapedPackageShaderPath << "\n"; return 1; } dependencies.Clear(); dependencyError.Clear(); if (CollectDependencies(escapedPackageShaderPath, dependencies, dependencyError)) { std::cerr << "CollectSourceDependencies unexpectedly accepted a package include outside Packages root\n"; return 1; } if (!RequireIncludeFailureDiagnostic( "escaped package include", dependencyError, "Packages/../forward-lit.shader")) { return 1; } const std::filesystem::path absoluteIncludeTargetPath = tempRoot / "absolute_include_target.hlsl"; if (!WriteTextFile(absoluteIncludeTargetPath, "float4 AbsoluteIncludeValue() { return 1.0f.xxxx; }\n")) { std::cerr << "failed to write absolute include target fixture: " << absoluteIncludeTargetPath << "\n"; return 1; } const std::filesystem::path absoluteIncludeShaderPath = tempRoot / "absolute_include.shader"; const std::string absoluteIncludePath = absoluteIncludeTargetPath.generic_string(); const std::string absoluteIncludeShaderSource = "Shader \"Tests/AbsoluteInclude\"\n" "{\n" " SubShader\n" " {\n" " Pass\n" " {\n" " Name \"AbsoluteInclude\"\n" " HLSLPROGRAM\n" " #pragma vertex MainVS\n" " #pragma fragment MainPS\n" " #include \"" + absoluteIncludePath + "\"\n" + " float4 MainVS(float3 positionOS : POSITION) : SV_POSITION\n" " {\n" " return float4(positionOS, 1.0f);\n" " }\n" " float4 MainPS() : SV_TARGET\n" " {\n" " return AbsoluteIncludeValue();\n" " }\n" " ENDHLSL\n" " }\n" " }\n" "}\n"; if (!WriteTextFile(absoluteIncludeShaderPath, absoluteIncludeShaderSource.c_str())) { std::cerr << "failed to write absolute include shader fixture: " << absoluteIncludeShaderPath << "\n"; return 1; } dependencies.Clear(); dependencyError.Clear(); if (CollectDependencies(absoluteIncludeShaderPath, dependencies, dependencyError)) { std::cerr << "CollectSourceDependencies unexpectedly accepted an absolute include\n"; return 1; } if (!RequireIncludeFailureDiagnostic( "absolute include", dependencyError, absoluteIncludePath)) { return 1; } const std::filesystem::path assetDatabaseProjectRoot = tempRoot / "asset_database_project"; std::error_code cleanupError; std::filesystem::remove_all(assetDatabaseProjectRoot, cleanupError); if (!RequireAssetDatabaseImportFailureDiagnostic(assetDatabaseProjectRoot)) { return 1; } const std::filesystem::path artifactInvalidationProjectRoot = tempRoot / "artifact_invalidation_project"; if (!RequirePackageDependencyStaleRecordTriggersShaderReimport( artifactInvalidationProjectRoot)) { return 1; } const std::filesystem::path packageEditProjectRoot = tempRoot / "package_edit_artifact_project"; if (!RequirePackageEditProducesNewShaderArtifact(packageEditProjectRoot)) { return 1; } return 0; }