1228 lines
45 KiB
C++
1228 lines
45 KiB
C++
|
|
#include <XCEngine/Core/Asset/AssetDatabase.h>
|
||
|
|
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||
|
|
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||
|
|
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||
|
|
#include <XCEngine/Core/Containers/Array.h>
|
||
|
|
#include <XCEngine/Core/Containers/String.h>
|
||
|
|
#include <XCEngine/Rendering/Internal/ShaderVariantUtils.h>
|
||
|
|
#include <XCEngine/RHI/D3D12/D3D12Shader.h>
|
||
|
|
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||
|
|
|
||
|
|
#include <d3dcompiler.h>
|
||
|
|
|
||
|
|
#include <cstring>
|
||
|
|
#include <filesystem>
|
||
|
|
#include <fstream>
|
||
|
|
#include <iostream>
|
||
|
|
#include <memory>
|
||
|
|
#include <string>
|
||
|
|
#include <system_error>
|
||
|
|
#include <vector>
|
||
|
|
|
||
|
|
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<XCEngine::Containers::String>& 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<std::string> SplitTabFields(const std::string& line) {
|
||
|
|
std::vector<std::string> 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<std::string>& 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<std::string> 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<XCEngine::Core::uint64>(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<XCEngine::Core::uint64>(writeTime.time_since_epoch().count());
|
||
|
|
}
|
||
|
|
|
||
|
|
bool WriteMinimalCurrentShaderArtifact(const std::filesystem::path& artifactPath) {
|
||
|
|
XCEngine::Resources::ShaderArtifactFileHeader fileHeader = {};
|
||
|
|
XCEngine::Resources::ShaderArtifactHeader shaderHeader = {};
|
||
|
|
|
||
|
|
XCEngine::Containers::Array<XCEngine::Core::uint8> 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<std::string>& 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::Core::uint32>(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::Core::uint32>(
|
||
|
|
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<bool>(output);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool CollectDependencies(
|
||
|
|
const std::filesystem::path& shaderPath,
|
||
|
|
XCEngine::Containers::Array<XCEngine::Containers::String>& dependencies) {
|
||
|
|
XCEngine::Resources::ShaderLoader loader;
|
||
|
|
return loader.CollectSourceDependencies(shaderPath.generic_string().c_str(), dependencies);
|
||
|
|
}
|
||
|
|
|
||
|
|
bool CollectDependencies(
|
||
|
|
const std::filesystem::path& shaderPath,
|
||
|
|
XCEngine::Containers::Array<XCEngine::Containers::String>& 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<char>(ch));
|
||
|
|
}
|
||
|
|
|
||
|
|
return result;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool RequireDependencySuffixes(
|
||
|
|
const XCEngine::Containers::Array<XCEngine::Containers::String>& dependencies,
|
||
|
|
const std::vector<const char*>& 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<XCEngine::Containers::String>& 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<XCEngine::Containers::String>& 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<std::string> macroNames;
|
||
|
|
std::vector<std::string> macroDefinitions;
|
||
|
|
std::vector<D3D_SHADER_MACRO> 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<XCEngine::Resources::Shader> shader(
|
||
|
|
static_cast<XCEngine::Resources::Shader*>(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<XCEngine::Containers::String> 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<XCEngine::Resources::Shader*>(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<std::string> 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<XCEngine::Containers::String> 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;
|
||
|
|
}
|