Implement initial Unity-style asset library cache

This commit is contained in:
2026-04-02 03:03:36 +08:00
parent 619856ab22
commit 4c167bec0e
29 changed files with 4818 additions and 73 deletions

View File

@@ -1,4 +1,6 @@
#include <XCEngine/Resources/Mesh/MeshLoader.h>
#include <XCEngine/Core/Asset/ArtifactFormats.h>
#include <XCEngine/Resources/BuiltinResources.h>
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
#include <XCEngine/Resources/Material/Material.h>
#include <XCEngine/Resources/Texture/Texture.h>
@@ -14,6 +16,7 @@
#include <filesystem>
#include <fstream>
#include <limits>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
@@ -507,6 +510,218 @@ void ProcessNode(const aiNode& node,
}
}
Containers::String ReadBinaryString(std::ifstream& stream) {
Core::uint32 length = 0;
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
if (!stream || length == 0) {
return Containers::String();
}
std::string buffer(length, '\0');
stream.read(buffer.data(), length);
if (!stream) {
return Containers::String();
}
return Containers::String(buffer.c_str());
}
void ApplyMaterialProperty(Material& material, const MaterialProperty& property) {
switch (property.type) {
case MaterialPropertyType::Float:
material.SetFloat(property.name, property.value.floatValue[0]);
break;
case MaterialPropertyType::Float2:
material.SetFloat2(property.name,
Math::Vector2(property.value.floatValue[0], property.value.floatValue[1]));
break;
case MaterialPropertyType::Float3:
material.SetFloat3(property.name,
Math::Vector3(property.value.floatValue[0], property.value.floatValue[1], property.value.floatValue[2]));
break;
case MaterialPropertyType::Float4:
material.SetFloat4(property.name,
Math::Vector4(property.value.floatValue[0],
property.value.floatValue[1],
property.value.floatValue[2],
property.value.floatValue[3]));
break;
case MaterialPropertyType::Int:
material.SetInt(property.name, property.value.intValue[0]);
break;
case MaterialPropertyType::Bool:
material.SetBool(property.name, property.value.boolValue);
break;
default:
break;
}
}
LoadResult LoadMeshArtifact(const Containers::String& path) {
std::ifstream input(path.CStr(), std::ios::binary);
if (!input.is_open()) {
return LoadResult(Containers::String("Failed to read mesh artifact: ") + path);
}
MeshArtifactHeader header;
input.read(reinterpret_cast<char*>(&header), sizeof(header));
if (!input) {
return LoadResult(Containers::String("Failed to parse mesh artifact header: ") + path);
}
const std::string magic(header.magic, header.magic + 7);
if (magic != "XCMESH1") {
return LoadResult(Containers::String("Invalid mesh artifact magic: ") + path);
}
auto mesh = std::make_unique<Mesh>();
IResource::ConstructParams params;
params.name = GetResourceNameFromPath(path);
params.path = path;
params.guid = ResourceGUID::Generate(path);
params.memorySize = 0;
mesh->Initialize(params);
Containers::Array<MeshSection> sections;
sections.Resize(header.sectionCount);
for (Core::uint32 index = 0; index < header.sectionCount; ++index) {
input.read(reinterpret_cast<char*>(&sections[index]), sizeof(MeshSection));
if (!input) {
return LoadResult(Containers::String("Failed to read mesh sections: ") + path);
}
}
Containers::Array<Core::uint8> vertexData;
vertexData.Resize(static_cast<size_t>(header.vertexDataSize));
if (header.vertexDataSize > 0) {
input.read(reinterpret_cast<char*>(vertexData.Data()), static_cast<std::streamsize>(header.vertexDataSize));
if (!input) {
return LoadResult(Containers::String("Failed to read mesh vertex data: ") + path);
}
}
Containers::Array<Core::uint8> indexData;
indexData.Resize(static_cast<size_t>(header.indexDataSize));
if (header.indexDataSize > 0) {
input.read(reinterpret_cast<char*>(indexData.Data()), static_cast<std::streamsize>(header.indexDataSize));
if (!input) {
return LoadResult(Containers::String("Failed to read mesh index data: ") + path);
}
}
mesh->SetVertexData(vertexData.Data(),
vertexData.Size(),
header.vertexCount,
header.vertexStride,
static_cast<VertexAttribute>(header.vertexAttributes));
mesh->SetIndexData(indexData.Data(),
indexData.Size(),
header.indexCount,
header.use32BitIndex != 0);
for (const MeshSection& section : sections) {
mesh->AddSection(section);
}
Math::Bounds bounds;
bounds.SetMinMax(header.boundsMin, header.boundsMax);
mesh->SetBounds(bounds);
std::vector<Containers::String> textureFiles;
textureFiles.reserve(header.textureCount);
for (Core::uint32 textureIndex = 0; textureIndex < header.textureCount; ++textureIndex) {
textureFiles.push_back(ReadBinaryString(input));
}
std::unordered_map<std::string, Texture*> loadedTextures;
TextureLoader textureLoader;
const std::filesystem::path artifactDirectory = std::filesystem::path(path.CStr()).parent_path();
for (Core::uint32 materialIndex = 0; materialIndex < header.materialCount; ++materialIndex) {
Core::uint32 materialPresent = 0;
input.read(reinterpret_cast<char*>(&materialPresent), sizeof(materialPresent));
if (!input) {
return LoadResult(Containers::String("Failed to read mesh material flag: ") + path);
}
if (materialPresent == 0) {
mesh->AddMaterial(nullptr);
continue;
}
auto* material = new Material();
material->m_name = ReadBinaryString(input);
material->m_path = ReadBinaryString(input);
material->m_guid = ResourceGUID::Generate(material->m_path);
material->m_isValid = true;
material->SetShaderPass(ReadBinaryString(input));
MaterialArtifactHeader materialHeader;
input.read(reinterpret_cast<char*>(&materialHeader), sizeof(materialHeader));
if (!input) {
delete material;
return LoadResult(Containers::String("Failed to read material artifact header: ") + path);
}
material->SetRenderQueue(materialHeader.renderQueue);
material->SetRenderState(materialHeader.renderState);
for (Core::uint32 tagIndex = 0; tagIndex < materialHeader.tagCount; ++tagIndex) {
material->SetTag(ReadBinaryString(input), ReadBinaryString(input));
}
for (Core::uint32 propertyIndex = 0; propertyIndex < materialHeader.propertyCount; ++propertyIndex) {
MaterialProperty property;
property.name = ReadBinaryString(input);
MaterialPropertyArtifact propertyArtifact;
input.read(reinterpret_cast<char*>(&propertyArtifact), sizeof(propertyArtifact));
if (!input) {
delete material;
return LoadResult(Containers::String("Failed to read material property: ") + path);
}
property.type = static_cast<MaterialPropertyType>(propertyArtifact.propertyType);
property.value = propertyArtifact.value;
ApplyMaterialProperty(*material, property);
}
for (Core::uint32 bindingIndex = 0; bindingIndex < materialHeader.textureBindingCount; ++bindingIndex) {
const Containers::String bindingName = ReadBinaryString(input);
const Containers::String textureFile = ReadBinaryString(input);
if (textureFile.Empty()) {
continue;
}
const std::string textureKey(textureFile.CStr());
Texture* texture = nullptr;
auto textureIt = loadedTextures.find(textureKey);
if (textureIt != loadedTextures.end()) {
texture = textureIt->second;
} else {
const Containers::String texturePath =
Containers::String((artifactDirectory / textureFile.CStr()).lexically_normal().string().c_str());
LoadResult textureResult = textureLoader.Load(texturePath);
if (textureResult && textureResult.resource != nullptr) {
texture = static_cast<Texture*>(textureResult.resource);
loadedTextures.emplace(textureKey, texture);
mesh->AddTexture(texture);
}
}
if (texture != nullptr) {
material->SetTexture(bindingName, ResourceHandle<Texture>(texture));
}
}
material->RecalculateMemorySize();
mesh->AddMaterial(material);
}
return LoadResult(mesh.release());
}
} // namespace
MeshLoader::MeshLoader() = default;
@@ -520,26 +735,45 @@ Containers::Array<Containers::String> MeshLoader::GetSupportedExtensions() const
extensions.PushBack(Containers::String("glb"));
extensions.PushBack(Containers::String("dae"));
extensions.PushBack(Containers::String("stl"));
extensions.PushBack(Containers::String("xcmesh"));
return extensions;
}
bool MeshLoader::CanLoad(const Containers::String& path) const {
if (IsBuiltinMeshPath(path)) {
return true;
}
Containers::String ext = GetExtension(path).ToLower();
return ext == "fbx" || ext == "obj" || ext == "gltf" ||
ext == "glb" || ext == "dae" || ext == "stl";
ext == "glb" || ext == "dae" || ext == "stl" ||
ext == "xcmesh";
}
LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings* settings) {
if (IsBuiltinMeshPath(path)) {
return CreateBuiltinMeshResource(path);
}
const Containers::String ext = GetExtension(path).ToLower();
if (!CanLoad(path)) {
return LoadResult(Containers::String("Unsupported mesh format: ") + ext);
}
std::ifstream file(path.CStr(), std::ios::binary);
if (ext == "xcmesh") {
return LoadMeshArtifact(path);
}
Containers::String resolvedPath = path;
if (!std::filesystem::path(path.CStr()).is_absolute()) {
resolvedPath = ResourceManager::Get().ResolvePath(path);
}
std::ifstream file(resolvedPath.CStr(), std::ios::binary);
if (!file.is_open()) {
return LoadResult(Containers::String("Failed to read file: ") + path);
return LoadResult(Containers::String("Failed to read file: ") + resolvedPath);
}
MeshImportSettings defaultSettings;
@@ -549,7 +783,7 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
}
Assimp::Importer importer;
const aiScene* scene = importer.ReadFile(path.CStr(), BuildPostProcessFlags(*resolvedSettings));
const aiScene* scene = importer.ReadFile(resolvedPath.CStr(), BuildPostProcessFlags(*resolvedSettings));
if (scene == nullptr || scene->mRootNode == nullptr || (scene->mFlags & AI_SCENE_FLAGS_INCOMPLETE) != 0) {
const char* errorText = importer.GetErrorString();
return LoadResult(Containers::String("Assimp failed to load mesh: ") +
@@ -577,7 +811,7 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
auto* mesh = new Mesh();
IResource::ConstructParams params;
const std::string fileName = std::filesystem::path(path.CStr()).filename().string();
const std::string fileName = std::filesystem::path(resolvedPath.CStr()).filename().string();
params.name = Containers::String(fileName.c_str());
params.path = path;
params.guid = ResourceGUID::Generate(path);