refactor: route builtin outline pass through shader assets
This commit is contained in:
@@ -13,6 +13,7 @@
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <unordered_map>
|
||||
#include <unordered_set>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -48,6 +49,32 @@ Containers::String NormalizeArtifactPathString(const Containers::String& path) {
|
||||
return ToContainersString(fs::path(path.CStr()).lexically_normal().generic_string());
|
||||
}
|
||||
|
||||
bool IsProjectRelativePath(const fs::path& path) {
|
||||
const std::string generic = path.generic_string();
|
||||
return !generic.empty() &&
|
||||
generic != "." &&
|
||||
generic != ".." &&
|
||||
generic.rfind("../", 0) != 0;
|
||||
}
|
||||
|
||||
void AddUniqueDependencyPath(const fs::path& path,
|
||||
std::unordered_set<std::string>& seenPaths,
|
||||
std::vector<fs::path>& outPaths) {
|
||||
if (path.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fs::path normalizedPath = path.lexically_normal();
|
||||
const std::string key = normalizedPath.generic_string();
|
||||
if (key.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (seenPaths.insert(key).second) {
|
||||
outPaths.push_back(normalizedPath);
|
||||
}
|
||||
}
|
||||
|
||||
std::string TrimCopy(const std::string& text) {
|
||||
const auto begin = std::find_if_not(text.begin(), text.end(), [](unsigned char ch) {
|
||||
return std::isspace(ch) != 0;
|
||||
@@ -69,6 +96,91 @@ std::string ToLowerCopy(std::string text) {
|
||||
return text;
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitWhitespaceTokens(const std::string& text) {
|
||||
std::vector<std::string> tokens;
|
||||
std::istringstream stream(text);
|
||||
std::string token;
|
||||
while (stream >> token) {
|
||||
tokens.push_back(token);
|
||||
}
|
||||
return tokens;
|
||||
}
|
||||
|
||||
std::vector<fs::path> CollectObjDeclaredDependencyPaths(const fs::path& sourcePath) {
|
||||
std::vector<fs::path> dependencies;
|
||||
std::unordered_set<std::string> seenPaths;
|
||||
|
||||
std::ifstream input(sourcePath);
|
||||
if (!input.is_open()) {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
const fs::path sourceDirectory = sourcePath.parent_path();
|
||||
std::string line;
|
||||
while (std::getline(input, line)) {
|
||||
const std::string trimmed = TrimCopy(line);
|
||||
if (trimmed.empty() || trimmed[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string lowered = ToLowerCopy(trimmed);
|
||||
if (lowered.rfind("mtllib", 0) != 0 ||
|
||||
(trimmed.size() > 6 && std::isspace(static_cast<unsigned char>(trimmed[6])) == 0)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string remainder = TrimCopy(trimmed.substr(6));
|
||||
for (const std::string& token : SplitWhitespaceTokens(remainder)) {
|
||||
AddUniqueDependencyPath(sourceDirectory / token, seenPaths, dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
std::vector<fs::path> CollectMtlDeclaredDependencyPaths(const fs::path& mtlPath) {
|
||||
std::vector<fs::path> dependencies;
|
||||
std::unordered_set<std::string> seenPaths;
|
||||
|
||||
std::ifstream input(mtlPath);
|
||||
if (!input.is_open()) {
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
const fs::path sourceDirectory = mtlPath.parent_path();
|
||||
std::string line;
|
||||
while (std::getline(input, line)) {
|
||||
const std::string trimmed = TrimCopy(line);
|
||||
if (trimmed.empty() || trimmed[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::vector<std::string> tokens = SplitWhitespaceTokens(trimmed);
|
||||
if (tokens.size() < 2) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string keyword = ToLowerCopy(tokens.front());
|
||||
const bool isTextureDirective =
|
||||
keyword.rfind("map_", 0) == 0 ||
|
||||
keyword == "bump" ||
|
||||
keyword == "disp" ||
|
||||
keyword == "decal" ||
|
||||
keyword == "refl" ||
|
||||
keyword == "norm";
|
||||
if (!isTextureDirective) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string& textureToken = tokens.back();
|
||||
if (!textureToken.empty() && textureToken[0] != '-') {
|
||||
AddUniqueDependencyPath(sourceDirectory / textureToken, seenPaths, dependencies);
|
||||
}
|
||||
}
|
||||
|
||||
return dependencies;
|
||||
}
|
||||
|
||||
std::string EscapeField(const std::string& value) {
|
||||
std::string escaped;
|
||||
escaped.reserve(value.size());
|
||||
@@ -589,6 +701,16 @@ void AssetDatabase::LoadArtifactDB() {
|
||||
record.sourceFileSize = static_cast<Core::uint64>(std::stoull(fields[9]));
|
||||
record.sourceWriteTime = fields.size() > 10 ? static_cast<Core::uint64>(std::stoull(fields[10])) : 0;
|
||||
record.mainLocalID = fields.size() > 11 ? static_cast<LocalID>(std::stoull(fields[11])) : kMainAssetLocalID;
|
||||
for (size_t index = 12; index + 3 < fields.size(); index += 4) {
|
||||
ArtifactDependencyRecord dependency;
|
||||
dependency.path = ToContainersString(fields[index + 0]);
|
||||
dependency.hash = ToContainersString(fields[index + 1]);
|
||||
dependency.fileSize = static_cast<Core::uint64>(std::stoull(fields[index + 2]));
|
||||
dependency.writeTime = static_cast<Core::uint64>(std::stoull(fields[index + 3]));
|
||||
if (!dependency.path.Empty()) {
|
||||
record.dependencies.push_back(dependency);
|
||||
}
|
||||
}
|
||||
|
||||
if (!record.assetGuid.IsValid() || record.artifactKey.Empty()) {
|
||||
continue;
|
||||
@@ -604,7 +726,7 @@ void AssetDatabase::SaveArtifactDB() const {
|
||||
return;
|
||||
}
|
||||
|
||||
output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\n";
|
||||
output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\t(depPath\tdepHash\tdepSize\tdepWriteTime)...\n";
|
||||
for (const auto& [guid, record] : m_artifactsByGuid) {
|
||||
output << EscapeField(ToStdString(record.artifactKey)) << '\t'
|
||||
<< EscapeField(ToStdString(record.assetGuid.ToString())) << '\t'
|
||||
@@ -617,7 +739,14 @@ void AssetDatabase::SaveArtifactDB() const {
|
||||
<< EscapeField(ToStdString(record.metaHash)) << '\t'
|
||||
<< record.sourceFileSize << '\t'
|
||||
<< record.sourceWriteTime << '\t'
|
||||
<< record.mainLocalID << '\n';
|
||||
<< record.mainLocalID;
|
||||
for (const ArtifactDependencyRecord& dependency : record.dependencies) {
|
||||
output << '\t' << EscapeField(ToStdString(dependency.path))
|
||||
<< '\t' << EscapeField(ToStdString(dependency.hash))
|
||||
<< '\t' << dependency.fileSize
|
||||
<< '\t' << dependency.writeTime;
|
||||
}
|
||||
output << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
@@ -714,6 +843,10 @@ bool AssetDatabase::EnsureMetaForPath(const fs::path& sourcePath,
|
||||
}
|
||||
shouldRewriteMeta = true;
|
||||
}
|
||||
if (outRecord.importerVersion != kCurrentImporterVersion) {
|
||||
outRecord.importerVersion = kCurrentImporterVersion;
|
||||
shouldRewriteMeta = true;
|
||||
}
|
||||
|
||||
const auto duplicateGuidIt = m_sourcesByGuid.find(outRecord.guid);
|
||||
if (duplicateGuidIt != m_sourcesByGuid.end() &&
|
||||
@@ -880,7 +1013,8 @@ bool AssetDatabase::ShouldReimport(const SourceAssetRecord& sourceRecord,
|
||||
artifactRecord->sourceHash != sourceRecord.sourceHash ||
|
||||
artifactRecord->metaHash != sourceRecord.metaHash ||
|
||||
artifactRecord->sourceFileSize != sourceRecord.sourceFileSize ||
|
||||
artifactRecord->sourceWriteTime != sourceRecord.sourceWriteTime;
|
||||
artifactRecord->sourceWriteTime != sourceRecord.sourceWriteTime ||
|
||||
!AreDependenciesCurrent(artifactRecord->dependencies);
|
||||
}
|
||||
|
||||
bool AssetDatabase::ImportAsset(const SourceAssetRecord& sourceRecord,
|
||||
@@ -1058,6 +1192,7 @@ bool AssetDatabase::ImportTextureAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
|
||||
outRecord.mainLocalID = kMainAssetLocalID;
|
||||
outRecord.dependencies.clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1072,7 +1207,9 @@ bool AssetDatabase::ImportMaterialAsset(const SourceAssetRecord& sourceRecord,
|
||||
}
|
||||
|
||||
Material* material = static_cast<Material*>(result.resource);
|
||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
||||
std::vector<ArtifactDependencyRecord> dependencies;
|
||||
CollectMaterialDependencies(*material, dependencies);
|
||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
|
||||
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
||||
const Containers::String mainArtifactPath =
|
||||
NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcmat");
|
||||
@@ -1105,6 +1242,7 @@ bool AssetDatabase::ImportMaterialAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
|
||||
outRecord.mainLocalID = kMainAssetLocalID;
|
||||
outRecord.dependencies = std::move(dependencies);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -1118,7 +1256,9 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
|
||||
}
|
||||
|
||||
Mesh* mesh = static_cast<Mesh*>(result.resource);
|
||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
||||
std::vector<ArtifactDependencyRecord> dependencies;
|
||||
CollectModelDependencies(sourceRecord, *mesh, dependencies);
|
||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord, dependencies);
|
||||
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
||||
const Containers::String mainArtifactPath = NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcmesh");
|
||||
|
||||
@@ -1196,10 +1336,13 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
|
||||
outRecord.mainLocalID = kMainAssetLocalID;
|
||||
outRecord.dependencies = std::move(dependencies);
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String AssetDatabase::BuildArtifactKey(const SourceAssetRecord& sourceRecord) const {
|
||||
Containers::String AssetDatabase::BuildArtifactKey(
|
||||
const AssetDatabase::SourceAssetRecord& sourceRecord,
|
||||
const std::vector<AssetDatabase::ArtifactDependencyRecord>& dependencies) const {
|
||||
Containers::String signature = sourceRecord.guid.ToString();
|
||||
signature += ":";
|
||||
signature += sourceRecord.importerName;
|
||||
@@ -1213,9 +1356,176 @@ Containers::String AssetDatabase::BuildArtifactKey(const SourceAssetRecord& sour
|
||||
signature += Containers::String(std::to_string(sourceRecord.sourceFileSize).c_str());
|
||||
signature += ":";
|
||||
signature += Containers::String(std::to_string(sourceRecord.sourceWriteTime).c_str());
|
||||
for (const ArtifactDependencyRecord& dependency : dependencies) {
|
||||
signature += ":dep:";
|
||||
signature += dependency.path;
|
||||
signature += ":";
|
||||
signature += dependency.hash;
|
||||
signature += ":";
|
||||
signature += Containers::String(std::to_string(dependency.fileSize).c_str());
|
||||
signature += ":";
|
||||
signature += Containers::String(std::to_string(dependency.writeTime).c_str());
|
||||
}
|
||||
return HashStringToAssetGUID(signature).ToString();
|
||||
}
|
||||
|
||||
Containers::String AssetDatabase::NormalizeDependencyPath(const fs::path& path) const {
|
||||
const fs::path normalizedPath = path.lexically_normal();
|
||||
if (normalizedPath.empty()) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
if (normalizedPath.is_absolute() && !m_projectRoot.Empty()) {
|
||||
std::error_code ec;
|
||||
const fs::path relativePath = fs::relative(normalizedPath, fs::path(m_projectRoot.CStr()), ec);
|
||||
if (!ec && IsProjectRelativePath(relativePath)) {
|
||||
return ToContainersString(relativePath.generic_string());
|
||||
}
|
||||
}
|
||||
|
||||
return ToContainersString(normalizedPath.generic_string());
|
||||
}
|
||||
|
||||
fs::path AssetDatabase::ResolveDependencyPath(const Containers::String& path) const {
|
||||
if (path.Empty()) {
|
||||
return fs::path();
|
||||
}
|
||||
|
||||
fs::path dependencyPath(path.CStr());
|
||||
if (dependencyPath.is_absolute()) {
|
||||
return dependencyPath.lexically_normal();
|
||||
}
|
||||
|
||||
return (fs::path(m_projectRoot.CStr()) / dependencyPath).lexically_normal();
|
||||
}
|
||||
|
||||
bool AssetDatabase::CaptureDependencyRecord(const fs::path& path,
|
||||
AssetDatabase::ArtifactDependencyRecord& outRecord) const {
|
||||
const Containers::String normalizedPath = NormalizeDependencyPath(path);
|
||||
if (normalizedPath.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRecord = ArtifactDependencyRecord();
|
||||
outRecord.path = normalizedPath;
|
||||
|
||||
const fs::path resolvedPath = ResolveDependencyPath(normalizedPath);
|
||||
if (!resolvedPath.empty() && fs::exists(resolvedPath)) {
|
||||
outRecord.hash = ComputeFileHash(resolvedPath);
|
||||
outRecord.fileSize = GetFileSizeValue(resolvedPath);
|
||||
outRecord.writeTime = GetFileWriteTimeValue(resolvedPath);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetDatabase::AreDependenciesCurrent(
|
||||
const std::vector<AssetDatabase::ArtifactDependencyRecord>& dependencies) const {
|
||||
for (const AssetDatabase::ArtifactDependencyRecord& dependency : dependencies) {
|
||||
if (dependency.path.Empty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const fs::path resolvedPath = ResolveDependencyPath(dependency.path);
|
||||
if (resolvedPath.empty() || !fs::exists(resolvedPath)) {
|
||||
if (!dependency.hash.Empty() ||
|
||||
dependency.fileSize != 0 ||
|
||||
dependency.writeTime != 0) {
|
||||
return false;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
const Core::uint64 currentFileSize = GetFileSizeValue(resolvedPath);
|
||||
const Core::uint64 currentWriteTime = GetFileWriteTimeValue(resolvedPath);
|
||||
if (currentFileSize != dependency.fileSize ||
|
||||
currentWriteTime != dependency.writeTime) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (ComputeFileHash(resolvedPath) != dependency.hash) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetDatabase::CollectModelDependencies(const AssetDatabase::SourceAssetRecord& sourceRecord,
|
||||
const Mesh& mesh,
|
||||
std::vector<AssetDatabase::ArtifactDependencyRecord>& outDependencies) const {
|
||||
outDependencies.clear();
|
||||
|
||||
std::unordered_set<std::string> seenDependencyPaths;
|
||||
std::vector<fs::path> candidatePaths;
|
||||
|
||||
const fs::path sourcePath = fs::path(m_projectRoot.CStr()) / sourceRecord.relativePath.CStr();
|
||||
const fs::path normalizedSourcePath = sourcePath.lexically_normal();
|
||||
if (ToLowerCopy(normalizedSourcePath.extension().string()) == ".obj") {
|
||||
const std::vector<fs::path> mtlPaths = CollectObjDeclaredDependencyPaths(normalizedSourcePath);
|
||||
for (const fs::path& mtlPath : mtlPaths) {
|
||||
AddUniqueDependencyPath(mtlPath, seenDependencyPaths, candidatePaths);
|
||||
const std::vector<fs::path> texturePaths = CollectMtlDeclaredDependencyPaths(mtlPath);
|
||||
for (const fs::path& texturePath : texturePaths) {
|
||||
AddUniqueDependencyPath(texturePath, seenDependencyPaths, candidatePaths);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (Texture* texture : mesh.GetTextures()) {
|
||||
if (texture == nullptr || texture->GetPath().Empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string texturePath = ToStdString(texture->GetPath());
|
||||
if (texturePath.find('#') != std::string::npos) {
|
||||
continue;
|
||||
}
|
||||
|
||||
AddUniqueDependencyPath(fs::path(texturePath), seenDependencyPaths, candidatePaths);
|
||||
}
|
||||
|
||||
const std::string sourcePathKey = normalizedSourcePath.generic_string();
|
||||
for (const fs::path& candidatePath : candidatePaths) {
|
||||
if (candidatePath.generic_string() == sourcePathKey) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ArtifactDependencyRecord dependency;
|
||||
if (CaptureDependencyRecord(candidatePath, dependency)) {
|
||||
outDependencies.push_back(dependency);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetDatabase::CollectMaterialDependencies(
|
||||
const Material& material,
|
||||
std::vector<AssetDatabase::ArtifactDependencyRecord>& outDependencies) const {
|
||||
outDependencies.clear();
|
||||
|
||||
std::unordered_set<std::string> seenDependencyPaths;
|
||||
for (Core::uint32 bindingIndex = 0; bindingIndex < material.GetTextureBindingCount(); ++bindingIndex) {
|
||||
const Containers::String texturePath = material.GetTextureBindingPath(bindingIndex);
|
||||
if (texturePath.Empty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ArtifactDependencyRecord dependency;
|
||||
if (!CaptureDependencyRecord(ResolveDependencyPath(texturePath), dependency)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string dependencyKey = ToStdString(dependency.path);
|
||||
if (seenDependencyPaths.insert(dependencyKey).second) {
|
||||
outDependencies.push_back(dependency);
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String AssetDatabase::BuildArtifactDirectory(const Containers::String& artifactKey) const {
|
||||
if (artifactKey.Length() < 2) {
|
||||
return Containers::String("Library/Artifacts/00/invalid");
|
||||
|
||||
Reference in New Issue
Block a user