chore: checkpoint current workspace changes

This commit is contained in:
2026-04-11 22:14:02 +08:00
parent 3e55f8c204
commit 8848cfd958
227 changed files with 34027 additions and 6711 deletions

View File

@@ -27,6 +27,7 @@
#include "Rendering/Internal/ShaderVariantUtils.h"
#include <algorithm>
#include <array>
#include <cctype>
#include <cstring>
#include <d3dcompiler.h>
@@ -37,6 +38,10 @@
#include <unordered_set>
#include <vector>
#if defined(_WIN32)
#include <bcrypt.h>
#endif
namespace XCEngine {
namespace Resources {
@@ -44,6 +49,158 @@ namespace fs = std::filesystem;
namespace {
class IncrementalGuidHasher {
public:
void Append(const void* data, size_t size) {
if (data == nullptr || size == 0) {
return;
}
m_hasBytes = true;
const auto* bytes = static_cast<const Core::uint8*>(data);
for (size_t index = 0; index < size; ++index) {
m_high ^= static_cast<Core::uint64>(bytes[index]);
m_high *= 1099511628211ULL;
m_low ^= static_cast<Core::uint64>(bytes[index]);
m_low *= 1099511628211ULL;
}
}
AssetGUID Finish() const {
if (!m_hasBytes) {
return AssetGUID();
}
return AssetGUID(m_high, m_low);
}
private:
bool m_hasBytes = false;
Core::uint64 m_high = 14695981039346656037ULL;
Core::uint64 m_low = 1099511628211ULL ^ 0x9e3779b97f4a7c15ULL;
};
#if defined(_WIN32)
bool TryComputeFastFileHash(const fs::path& path, Containers::String& outHash) {
outHash.Clear();
std::ifstream input(path, std::ios::binary);
if (!input.is_open()) {
return false;
}
BCRYPT_ALG_HANDLE algorithm = nullptr;
BCRYPT_HASH_HANDLE hash = nullptr;
std::vector<UCHAR> hashObject;
std::vector<UCHAR> digest;
auto cleanup = [&]() {
if (hash != nullptr) {
BCryptDestroyHash(hash);
hash = nullptr;
}
if (algorithm != nullptr) {
BCryptCloseAlgorithmProvider(algorithm, 0);
algorithm = nullptr;
}
};
NTSTATUS status = BCryptOpenAlgorithmProvider(
&algorithm,
BCRYPT_SHA256_ALGORITHM,
nullptr,
0);
if (!BCRYPT_SUCCESS(status)) {
cleanup();
return false;
}
DWORD hashObjectSize = 0;
DWORD bytesCopied = 0;
status = BCryptGetProperty(
algorithm,
BCRYPT_OBJECT_LENGTH,
reinterpret_cast<PUCHAR>(&hashObjectSize),
sizeof(hashObjectSize),
&bytesCopied,
0);
if (!BCRYPT_SUCCESS(status) || hashObjectSize == 0) {
cleanup();
return false;
}
DWORD digestSize = 0;
status = BCryptGetProperty(
algorithm,
BCRYPT_HASH_LENGTH,
reinterpret_cast<PUCHAR>(&digestSize),
sizeof(digestSize),
&bytesCopied,
0);
if (!BCRYPT_SUCCESS(status) || digestSize == 0) {
cleanup();
return false;
}
hashObject.resize(hashObjectSize);
digest.resize(digestSize);
status = BCryptCreateHash(
algorithm,
&hash,
hashObject.data(),
static_cast<ULONG>(hashObject.size()),
nullptr,
0,
0);
if (!BCRYPT_SUCCESS(status)) {
cleanup();
return false;
}
std::array<char, 256 * 1024> buffer = {};
bool hasBytes = false;
while (input) {
input.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
const std::streamsize bytesRead = input.gcount();
if (bytesRead <= 0) {
continue;
}
hasBytes = true;
status = BCryptHashData(
hash,
reinterpret_cast<PUCHAR>(buffer.data()),
static_cast<ULONG>(bytesRead),
0);
if (!BCRYPT_SUCCESS(status)) {
cleanup();
return false;
}
}
if (!hasBytes) {
cleanup();
outHash = HashBytesToAssetGUID(nullptr, 0).ToString();
return true;
}
status = BCryptFinishHash(
hash,
digest.data(),
static_cast<ULONG>(digest.size()),
0);
if (!BCRYPT_SUCCESS(status)) {
cleanup();
return false;
}
cleanup();
outHash = HashBytesToAssetGUID(digest.data(), digest.size()).ToString();
return true;
}
#endif
std::string ToStdString(const Containers::String& value) {
return std::string(value.CStr());
}
@@ -86,6 +243,34 @@ constexpr const char* kSourceAssetDbFileName = "assets.db";
constexpr const char* kArtifactDbFileName = "artifacts.db";
constexpr const char* kLegacySourceAssetDbDirectory = "SourceAssetDB";
constexpr const char* kLegacyArtifactDbDirectory = "ArtifactDB";
constexpr Core::uint32 kArtifactDbSchemaVersion = 2;
ArtifactStorageKind InferArtifactStorageKind(const Containers::String& projectRoot,
const AssetDatabase::ArtifactRecord& record) {
if (!record.mainArtifactPath.Empty()) {
const Containers::String absoluteMainArtifactPath =
NormalizeArtifactPathString(fs::path(projectRoot.CStr()) / record.mainArtifactPath.CStr());
if (IsArtifactContainerFile(absoluteMainArtifactPath)) {
return ArtifactStorageKind::SingleFileContainer;
}
}
return record.artifactDirectory.Empty()
? ArtifactStorageKind::Unknown
: ArtifactStorageKind::LegacyDirectory;
}
Containers::String BuildArtifactMainEntryLoadPath(const Containers::String& artifactMainPath,
ArtifactStorageKind storageKind,
const Containers::String& mainEntryName) {
if (storageKind == ArtifactStorageKind::SingleFileContainer &&
!artifactMainPath.Empty() &&
!mainEntryName.Empty()) {
return BuildArtifactContainerEntryPath(artifactMainPath, mainEntryName);
}
return artifactMainPath;
}
struct ModelSubAssetManifestEntry {
LocalID localID = kInvalidLocalID;
@@ -107,10 +292,18 @@ void PopulateResolvedAssetResult(const Containers::String& projectRoot,
outAsset.relativePath = sourceRecord.relativePath;
outAsset.assetGuid = sourceRecord.guid;
outAsset.resourceType = artifactRecord.resourceType;
outAsset.artifactDirectory =
ToContainersString((fs::path(projectRoot.CStr()) / artifactRecord.artifactDirectory.CStr()).lexically_normal().generic_string());
outAsset.artifactMainPath =
ToContainersString((fs::path(projectRoot.CStr()) / artifactRecord.mainArtifactPath.CStr()).lexically_normal().generic_string());
outAsset.artifactStorageKind = artifactRecord.storageKind;
outAsset.mainEntryName = artifactRecord.mainEntryName;
if (!artifactRecord.artifactDirectory.Empty()) {
outAsset.artifactDirectory = ToContainersString(
(fs::path(projectRoot.CStr()) / artifactRecord.artifactDirectory.CStr()).lexically_normal().generic_string());
}
outAsset.artifactMainPath = ToContainersString(
(fs::path(projectRoot.CStr()) / artifactRecord.mainArtifactPath.CStr()).lexically_normal().generic_string());
outAsset.artifactMainEntryPath = BuildArtifactMainEntryLoadPath(
outAsset.artifactMainPath,
artifactRecord.storageKind,
artifactRecord.mainEntryName);
outAsset.mainLocalID = artifactRecord.mainLocalID;
}
@@ -1753,8 +1946,25 @@ void AssetDatabase::LoadArtifactDB() {
}
std::string line;
Core::uint32 schemaVersion = 1;
while (std::getline(input, line)) {
if (line.empty() || line[0] == '#') {
if (line.empty()) {
continue;
}
if (line[0] == '#') {
const std::string schemaToken = "schema=";
const size_t schemaTokenPosition = line.find(schemaToken);
if (schemaTokenPosition != std::string::npos) {
const size_t valueStart = schemaTokenPosition + schemaToken.length();
const std::string valueText = line.substr(valueStart);
if (!valueText.empty()) {
schemaVersion = static_cast<Core::uint32>(std::stoul(valueText));
}
} else if (line.find("storageKind") != std::string::npos &&
line.find("mainEntryName") != std::string::npos) {
schemaVersion = std::max(schemaVersion, kArtifactDbSchemaVersion);
}
continue;
}
@@ -1776,7 +1986,26 @@ 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) {
size_t dependencyFieldStart = 12;
if (schemaVersion >= kArtifactDbSchemaVersion && fields.size() > 13) {
record.storageKind = static_cast<ArtifactStorageKind>(std::stoul(fields[12]));
record.mainEntryName = ToContainersString(fields[13]);
dependencyFieldStart = 14;
}
if (record.storageKind == ArtifactStorageKind::Unknown) {
record.storageKind = InferArtifactStorageKind(m_projectRoot, record);
}
if (record.mainEntryName.Empty() &&
record.storageKind == ArtifactStorageKind::SingleFileContainer) {
record.mainEntryName = "main";
}
if (record.artifactDirectory.Empty() && !record.mainArtifactPath.Empty()) {
record.artifactDirectory =
NormalizePathString(fs::path(record.mainArtifactPath.CStr()).parent_path());
}
for (size_t index = dependencyFieldStart; index + 3 < fields.size(); index += 4) {
ArtifactDependencyRecord dependency;
dependency.path = ToContainersString(fields[index + 0]);
dependency.hash = ToContainersString(fields[index + 1]);
@@ -1801,7 +2030,8 @@ void AssetDatabase::SaveArtifactDB() const {
return;
}
output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\t(depPath\tdepHash\tdepSize\tdepWriteTime)...\n";
output << "# schema=" << kArtifactDbSchemaVersion << "\n";
output << "# artifactKey\tassetGuid\timporter\tversion\ttype\tartifactDir\tmainArtifact\tsourceHash\tmetaHash\tsize\twriteTime\tmainLocalID\tstorageKind\tmainEntryName\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'
@@ -1814,7 +2044,9 @@ void AssetDatabase::SaveArtifactDB() const {
<< EscapeField(ToStdString(record.metaHash)) << '\t'
<< record.sourceFileSize << '\t'
<< record.sourceWriteTime << '\t'
<< record.mainLocalID;
<< record.mainLocalID << '\t'
<< static_cast<Core::uint32>(record.storageKind) << '\t'
<< EscapeField(ToStdString(record.mainEntryName));
for (const ArtifactDependencyRecord& dependency : record.dependencies) {
output << '\t' << EscapeField(ToStdString(dependency.path))
<< '\t' << EscapeField(ToStdString(dependency.hash))
@@ -2494,8 +2726,10 @@ bool AssetDatabase::ImportTextureAsset(const SourceAssetRecord& sourceRecord,
outRecord.importerName = sourceRecord.importerName;
outRecord.importerVersion = sourceRecord.importerVersion;
outRecord.resourceType = ResourceType::Texture;
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
outRecord.artifactDirectory = artifactDir;
outRecord.mainArtifactPath = mainArtifactPath;
outRecord.mainEntryName = "main";
outRecord.sourceHash = sourceRecord.sourceHash;
outRecord.metaHash = sourceRecord.metaHash;
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
@@ -2553,8 +2787,10 @@ bool AssetDatabase::ImportMaterialAsset(const SourceAssetRecord& sourceRecord,
outRecord.importerName = sourceRecord.importerName;
outRecord.importerVersion = sourceRecord.importerVersion;
outRecord.resourceType = ResourceType::Material;
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
outRecord.artifactDirectory = artifactDir;
outRecord.mainArtifactPath = mainArtifactPath;
outRecord.mainEntryName = "main";
outRecord.sourceHash = sourceRecord.sourceHash;
outRecord.metaHash = sourceRecord.metaHash;
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
@@ -2743,8 +2979,10 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
outRecord.importerName = sourceRecord.importerName;
outRecord.importerVersion = sourceRecord.importerVersion;
outRecord.resourceType = ResourceType::Model;
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
outRecord.artifactDirectory = artifactDir;
outRecord.mainArtifactPath = mainArtifactPath;
outRecord.mainEntryName = "main";
outRecord.sourceHash = sourceRecord.sourceHash;
outRecord.metaHash = sourceRecord.metaHash;
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
@@ -2809,8 +3047,10 @@ bool AssetDatabase::ImportShaderAsset(const SourceAssetRecord& sourceRecord,
outRecord.importerName = sourceRecord.importerName;
outRecord.importerVersion = sourceRecord.importerVersion;
outRecord.resourceType = ResourceType::Shader;
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
outRecord.artifactDirectory = artifactDir;
outRecord.mainArtifactPath = mainArtifactPath;
outRecord.mainEntryName = "main";
outRecord.sourceHash = sourceRecord.sourceHash;
outRecord.metaHash = sourceRecord.metaHash;
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
@@ -2874,8 +3114,10 @@ bool AssetDatabase::ImportGaussianSplatAsset(const SourceAssetRecord& sourceReco
outRecord.importerName = sourceRecord.importerName;
outRecord.importerVersion = sourceRecord.importerVersion;
outRecord.resourceType = ResourceType::GaussianSplat;
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
outRecord.artifactDirectory = artifactDir;
outRecord.mainArtifactPath = mainArtifactPath;
outRecord.mainEntryName = "main";
outRecord.sourceHash = sourceRecord.sourceHash;
outRecord.metaHash = sourceRecord.metaHash;
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
@@ -2953,8 +3195,10 @@ bool AssetDatabase::ImportVolumeFieldAsset(const SourceAssetRecord& sourceRecord
outRecord.importerName = sourceRecord.importerName;
outRecord.importerVersion = sourceRecord.importerVersion;
outRecord.resourceType = ResourceType::VolumeField;
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
outRecord.artifactDirectory = artifactDir;
outRecord.mainArtifactPath = mainArtifactPath;
outRecord.mainEntryName = "main";
outRecord.sourceHash = sourceRecord.sourceHash;
outRecord.metaHash = sourceRecord.metaHash;
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
@@ -3040,8 +3284,10 @@ bool AssetDatabase::ImportUIDocumentAsset(const SourceAssetRecord& sourceRecord,
outRecord.importerName = sourceRecord.importerName;
outRecord.importerVersion = sourceRecord.importerVersion;
outRecord.resourceType = resourceType;
outRecord.storageKind = ArtifactStorageKind::SingleFileContainer;
outRecord.artifactDirectory = artifactDir;
outRecord.mainArtifactPath = mainArtifactPath;
outRecord.mainEntryName = "main";
outRecord.sourceHash = sourceRecord.sourceHash;
outRecord.metaHash = sourceRecord.metaHash;
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
@@ -3315,19 +3561,31 @@ Containers::String AssetDatabase::ReadWholeFileText(const fs::path& path) {
}
Containers::String AssetDatabase::ComputeFileHash(const fs::path& path) {
#if defined(_WIN32)
Containers::String fastHash;
if (TryComputeFastFileHash(path, fastHash)) {
return fastHash;
}
#endif
std::ifstream input(path, std::ios::binary);
if (!input.is_open()) {
return Containers::String();
}
std::vector<Core::uint8> bytes(
(std::istreambuf_iterator<char>(input)),
std::istreambuf_iterator<char>());
if (bytes.empty()) {
return HashBytesToAssetGUID(nullptr, 0).ToString();
IncrementalGuidHasher hasher;
std::array<char, 64 * 1024> buffer = {};
while (input) {
input.read(buffer.data(), static_cast<std::streamsize>(buffer.size()));
const std::streamsize bytesRead = input.gcount();
if (bytesRead <= 0) {
continue;
}
hasher.Append(buffer.data(), static_cast<size_t>(bytesRead));
}
return HashBytesToAssetGUID(bytes.data(), bytes.size()).ToString();
return hasher.Finish().ToString();
}
Core::uint64 AssetDatabase::GetFileSizeValue(const fs::path& path) {

View File

@@ -341,9 +341,17 @@ AssetImportService::ImportedAsset AssetImportService::ConvertResolvedAsset(
importedAsset.relativePath = resolvedAsset.relativePath;
importedAsset.assetGuid = resolvedAsset.assetGuid;
importedAsset.resourceType = resolvedAsset.resourceType;
importedAsset.artifactMainPath = resolvedAsset.artifactMainPath;
importedAsset.artifactMainEntryPath = resolvedAsset.artifactMainEntryPath;
importedAsset.runtimeLoadPath =
resolvedAsset.artifactReady ? resolvedAsset.artifactMainPath : resolvedAsset.absolutePath;
resolvedAsset.artifactReady
? (!resolvedAsset.artifactMainEntryPath.Empty()
? resolvedAsset.artifactMainEntryPath
: resolvedAsset.artifactMainPath)
: resolvedAsset.absolutePath;
importedAsset.artifactDirectory = resolvedAsset.artifactDirectory;
importedAsset.artifactStorageKind = resolvedAsset.artifactStorageKind;
importedAsset.mainEntryName = resolvedAsset.mainEntryName;
importedAsset.mainLocalID = resolvedAsset.mainLocalID;
return importedAsset;
}

View File

@@ -1,4 +1,5 @@
#include <XCEngine/Core/IO/IResourceLoader.h>
#include <XCEngine/Core/Asset/ArtifactContainer.h>
#include <XCEngine/Core/Asset/ResourceManager.h>
#include <filesystem>
#include <fstream>
@@ -53,16 +54,23 @@ Containers::Array<Core::uint8> IResourceLoader::ReadFileData(const Containers::S
}
Containers::String IResourceLoader::GetExtension(const Containers::String& path) {
Containers::String normalizedPath = path;
Containers::String containerPath;
Containers::String entryName;
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
normalizedPath = containerPath;
}
Containers::String ext;
size_t dotPos = Containers::String::npos;
for (size_t i = path.Length(); i > 0; --i) {
if (path[i - 1] == '.') {
for (size_t i = normalizedPath.Length(); i > 0; --i) {
if (normalizedPath[i - 1] == '.') {
dotPos = i - 1;
break;
}
}
if (dotPos != Containers::String::npos) {
ext = path.Substring(dotPos + 1);
ext = normalizedPath.Substring(dotPos + 1);
}
return ext;
}