refactor: route builtin outline pass through shader assets

This commit is contained in:
2026-04-02 20:18:39 +08:00
parent 1f29dfd611
commit 9ce779da43
7 changed files with 591 additions and 145 deletions

View File

@@ -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");