Split mesh artifacts into material and texture artifacts
This commit is contained in:
@@ -2,7 +2,9 @@
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Debug/Logger.h>
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Resources/Shader/Shader.h>
|
||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||
|
||||
#include <algorithm>
|
||||
@@ -39,6 +41,13 @@ Containers::String ToContainersString(const std::string& value) {
|
||||
return Containers::String(value.c_str());
|
||||
}
|
||||
|
||||
Containers::String NormalizeArtifactPathString(const Containers::String& path) {
|
||||
if (path.Empty()) {
|
||||
return Containers::String();
|
||||
}
|
||||
return ToContainersString(fs::path(path.CStr()).lexically_normal().generic_string());
|
||||
}
|
||||
|
||||
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;
|
||||
@@ -200,11 +209,45 @@ std::vector<MaterialProperty> GatherMaterialProperties(const Material& material)
|
||||
return material.GetProperties();
|
||||
}
|
||||
|
||||
void WriteMaterialBlock(std::ofstream& output,
|
||||
const Material& material,
|
||||
const std::unordered_map<const Texture*, std::string>& textureFileNames) {
|
||||
Containers::String ResolveTextureBindingPath(
|
||||
const Material& material,
|
||||
Core::uint32 bindingIndex,
|
||||
const std::unordered_map<const Texture*, Containers::String>& textureArtifactPaths) {
|
||||
const ResourceHandle<Texture> textureHandle = material.GetTextureBindingTexture(bindingIndex);
|
||||
const Texture* texture = textureHandle.Get();
|
||||
if (texture != nullptr) {
|
||||
const auto textureIt = textureArtifactPaths.find(texture);
|
||||
if (textureIt != textureArtifactPaths.end()) {
|
||||
return textureIt->second;
|
||||
}
|
||||
|
||||
if (!texture->GetPath().Empty()) {
|
||||
return NormalizeArtifactPathString(texture->GetPath());
|
||||
}
|
||||
}
|
||||
|
||||
return NormalizeArtifactPathString(material.GetTextureBindingPath(bindingIndex));
|
||||
}
|
||||
|
||||
bool WriteMaterialArtifactFile(
|
||||
const fs::path& artifactPath,
|
||||
const Material& material,
|
||||
const std::unordered_map<const Texture*, Containers::String>& textureArtifactPaths = {}) {
|
||||
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
MaterialArtifactFileHeader fileHeader;
|
||||
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
||||
|
||||
WriteString(output, material.GetName());
|
||||
WriteString(output, material.GetPath());
|
||||
WriteString(
|
||||
output,
|
||||
material.GetShader() != nullptr
|
||||
? material.GetShader()->GetPath()
|
||||
: Containers::String());
|
||||
WriteString(output, material.GetShaderPass());
|
||||
|
||||
MaterialArtifactHeader header;
|
||||
@@ -243,18 +286,17 @@ void WriteMaterialBlock(std::ofstream& output,
|
||||
|
||||
for (Core::uint32 bindingIndex = 0; bindingIndex < material.GetTextureBindingCount(); ++bindingIndex) {
|
||||
const Containers::String bindingName = material.GetTextureBindingName(bindingIndex);
|
||||
const Texture* texture = material.GetTextureBindingTexture(bindingIndex).Get();
|
||||
auto fileIt = texture != nullptr ? textureFileNames.find(texture) : textureFileNames.end();
|
||||
|
||||
WriteString(output, bindingName);
|
||||
WriteString(output,
|
||||
fileIt != textureFileNames.end()
|
||||
? ToContainersString(fileIt->second)
|
||||
: Containers::String());
|
||||
WriteString(output, ResolveTextureBindingPath(material, bindingIndex, textureArtifactPaths));
|
||||
}
|
||||
|
||||
return static_cast<bool>(output);
|
||||
}
|
||||
|
||||
bool WriteMeshArtifactFile(const fs::path& artifactPath, const Mesh& mesh) {
|
||||
bool WriteMeshArtifactFile(const fs::path& artifactPath,
|
||||
const Mesh& mesh,
|
||||
const std::vector<Containers::String>& materialArtifactPaths) {
|
||||
std::ofstream output(artifactPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
return false;
|
||||
@@ -267,8 +309,7 @@ bool WriteMeshArtifactFile(const fs::path& artifactPath, const Mesh& mesh) {
|
||||
header.indexCount = mesh.GetIndexCount();
|
||||
header.use32BitIndex = mesh.IsUse32BitIndex() ? 1u : 0u;
|
||||
header.sectionCount = static_cast<Core::uint32>(mesh.GetSections().Size());
|
||||
header.materialCount = static_cast<Core::uint32>(mesh.GetMaterials().Size());
|
||||
header.textureCount = static_cast<Core::uint32>(mesh.GetTextures().Size());
|
||||
header.materialCount = static_cast<Core::uint32>(materialArtifactPaths.size());
|
||||
header.boundsMin = mesh.GetBounds().GetMin();
|
||||
header.boundsMax = mesh.GetBounds().GetMax();
|
||||
header.vertexDataSize = static_cast<Core::uint64>(mesh.GetVertexDataSize());
|
||||
@@ -286,25 +327,8 @@ bool WriteMeshArtifactFile(const fs::path& artifactPath, const Mesh& mesh) {
|
||||
output.write(static_cast<const char*>(mesh.GetIndexData()), mesh.GetIndexDataSize());
|
||||
}
|
||||
|
||||
std::unordered_map<const Texture*, std::string> textureFileNames;
|
||||
for (size_t textureIndex = 0; textureIndex < mesh.GetTextures().Size(); ++textureIndex) {
|
||||
const Texture* texture = mesh.GetTextures()[textureIndex];
|
||||
if (texture == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::string fileName = "texture_" + std::to_string(textureIndex) + ".xctex";
|
||||
textureFileNames.emplace(texture, fileName);
|
||||
WriteString(output, ToContainersString(fileName));
|
||||
}
|
||||
|
||||
for (size_t materialIndex = 0; materialIndex < mesh.GetMaterials().Size(); ++materialIndex) {
|
||||
const Material* material = mesh.GetMaterials()[materialIndex];
|
||||
const Core::uint32 materialPresent = material != nullptr ? 1u : 0u;
|
||||
output.write(reinterpret_cast<const char*>(&materialPresent), sizeof(materialPresent));
|
||||
if (material != nullptr) {
|
||||
WriteMaterialBlock(output, *material, textureFileNames);
|
||||
}
|
||||
for (const Containers::String& materialArtifactPath : materialArtifactPaths) {
|
||||
WriteString(output, NormalizeArtifactPathString(materialArtifactPath));
|
||||
}
|
||||
|
||||
return static_cast<bool>(output);
|
||||
@@ -852,7 +876,8 @@ bool AssetDatabase::ShouldReimport(const SourceAssetRecord& sourceRecord,
|
||||
return true;
|
||||
}
|
||||
|
||||
return artifactRecord->sourceHash != sourceRecord.sourceHash ||
|
||||
return artifactRecord->importerVersion != sourceRecord.importerVersion ||
|
||||
artifactRecord->sourceHash != sourceRecord.sourceHash ||
|
||||
artifactRecord->metaHash != sourceRecord.metaHash ||
|
||||
artifactRecord->sourceFileSize != sourceRecord.sourceFileSize ||
|
||||
artifactRecord->sourceWriteTime != sourceRecord.sourceWriteTime;
|
||||
@@ -864,6 +889,8 @@ bool AssetDatabase::ImportAsset(const SourceAssetRecord& sourceRecord,
|
||||
switch (primaryType) {
|
||||
case ResourceType::Texture:
|
||||
return ImportTextureAsset(sourceRecord, outRecord);
|
||||
case ResourceType::Material:
|
||||
return ImportMaterialAsset(sourceRecord, outRecord);
|
||||
case ResourceType::Mesh:
|
||||
return ImportModelAsset(sourceRecord, outRecord);
|
||||
default:
|
||||
@@ -1034,6 +1061,53 @@ bool AssetDatabase::ImportTextureAsset(const SourceAssetRecord& sourceRecord,
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetDatabase::ImportMaterialAsset(const SourceAssetRecord& sourceRecord,
|
||||
ArtifactRecord& outRecord) {
|
||||
MaterialLoader loader;
|
||||
const Containers::String absolutePath =
|
||||
NormalizePathString(fs::path(m_projectRoot.CStr()) / sourceRecord.relativePath.CStr());
|
||||
LoadResult result = loader.Load(absolutePath);
|
||||
if (!result || result.resource == nullptr) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Material* material = static_cast<Material*>(result.resource);
|
||||
const Containers::String artifactKey = BuildArtifactKey(sourceRecord);
|
||||
const Containers::String artifactDir = BuildArtifactDirectory(artifactKey);
|
||||
const Containers::String mainArtifactPath =
|
||||
NormalizePathString(fs::path(artifactDir.CStr()) / "main.xcmat");
|
||||
|
||||
std::error_code ec;
|
||||
fs::remove_all(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
||||
ec.clear();
|
||||
fs::create_directories(fs::path(m_projectRoot.CStr()) / artifactDir.CStr(), ec);
|
||||
if (ec) {
|
||||
delete material;
|
||||
return false;
|
||||
}
|
||||
|
||||
const bool writeOk =
|
||||
WriteMaterialArtifactFile(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(), *material);
|
||||
delete material;
|
||||
if (!writeOk) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outRecord.artifactKey = artifactKey;
|
||||
outRecord.assetGuid = sourceRecord.guid;
|
||||
outRecord.importerName = sourceRecord.importerName;
|
||||
outRecord.importerVersion = sourceRecord.importerVersion;
|
||||
outRecord.resourceType = ResourceType::Material;
|
||||
outRecord.artifactDirectory = artifactDir;
|
||||
outRecord.mainArtifactPath = mainArtifactPath;
|
||||
outRecord.sourceHash = sourceRecord.sourceHash;
|
||||
outRecord.metaHash = sourceRecord.metaHash;
|
||||
outRecord.sourceFileSize = sourceRecord.sourceFileSize;
|
||||
outRecord.sourceWriteTime = sourceRecord.sourceWriteTime;
|
||||
outRecord.mainLocalID = kMainAssetLocalID;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
|
||||
ArtifactRecord& outRecord) {
|
||||
MeshLoader loader;
|
||||
@@ -1057,18 +1131,54 @@ bool AssetDatabase::ImportModelAsset(const SourceAssetRecord& sourceRecord,
|
||||
return false;
|
||||
}
|
||||
|
||||
bool writeOk = WriteMeshArtifactFile(fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(), *mesh);
|
||||
bool writeOk = true;
|
||||
std::unordered_map<const Texture*, Containers::String> textureArtifactPaths;
|
||||
for (size_t textureIndex = 0; writeOk && textureIndex < mesh->GetTextures().Size(); ++textureIndex) {
|
||||
Texture* texture = mesh->GetTextures()[textureIndex];
|
||||
if (texture == nullptr) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const fs::path textureArtifactPath =
|
||||
fs::path(m_projectRoot.CStr()) / artifactDir.CStr() / ("texture_" + std::to_string(textureIndex) + ".xctex");
|
||||
writeOk = WriteTextureArtifactFile(textureArtifactPath, *texture);
|
||||
const Containers::String textureArtifactPath =
|
||||
NormalizePathString(fs::path(artifactDir.CStr()) / ("texture_" + std::to_string(textureIndex) + ".xctex"));
|
||||
writeOk = WriteTextureArtifactFile(
|
||||
fs::path(m_projectRoot.CStr()) / textureArtifactPath.CStr(),
|
||||
*texture);
|
||||
if (!writeOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
textureArtifactPaths.emplace(texture, textureArtifactPath);
|
||||
}
|
||||
|
||||
std::vector<Containers::String> materialArtifactPaths;
|
||||
materialArtifactPaths.reserve(mesh->GetMaterials().Size());
|
||||
for (size_t materialIndex = 0; writeOk && materialIndex < mesh->GetMaterials().Size(); ++materialIndex) {
|
||||
Material* material = mesh->GetMaterials()[materialIndex];
|
||||
if (material == nullptr) {
|
||||
materialArtifactPaths.emplace_back();
|
||||
continue;
|
||||
}
|
||||
|
||||
const Containers::String materialArtifactPath =
|
||||
NormalizePathString(fs::path(artifactDir.CStr()) / ("material_" + std::to_string(materialIndex) + ".xcmat"));
|
||||
writeOk = WriteMaterialArtifactFile(
|
||||
fs::path(m_projectRoot.CStr()) / materialArtifactPath.CStr(),
|
||||
*material,
|
||||
textureArtifactPaths);
|
||||
if (!writeOk) {
|
||||
break;
|
||||
}
|
||||
|
||||
materialArtifactPaths.push_back(materialArtifactPath);
|
||||
}
|
||||
|
||||
writeOk = writeOk &&
|
||||
WriteMeshArtifactFile(
|
||||
fs::path(m_projectRoot.CStr()) / mainArtifactPath.CStr(),
|
||||
*mesh,
|
||||
materialArtifactPaths);
|
||||
|
||||
DestroyImportedMesh(mesh);
|
||||
if (!writeOk) {
|
||||
return false;
|
||||
|
||||
@@ -52,6 +52,15 @@ void RemoveTextureBindingByName(
|
||||
}
|
||||
}
|
||||
|
||||
void EnsureTextureProperty(Containers::HashMap<Containers::String, MaterialProperty>& properties,
|
||||
const Containers::String& name) {
|
||||
MaterialProperty prop;
|
||||
prop.name = name;
|
||||
prop.type = MaterialPropertyType::Texture;
|
||||
prop.refCount = 1;
|
||||
properties.Insert(name, prop);
|
||||
}
|
||||
|
||||
void WritePackedMaterialProperty(Core::uint8* destination, const MaterialProperty& property) {
|
||||
std::memset(destination, 0, kMaterialConstantSlotSize);
|
||||
|
||||
@@ -271,15 +280,13 @@ void Material::SetBool(const Containers::String& name, bool value) {
|
||||
}
|
||||
|
||||
void Material::SetTexture(const Containers::String& name, const ResourceHandle<Texture>& texture) {
|
||||
MaterialProperty prop;
|
||||
prop.name = name;
|
||||
prop.type = MaterialPropertyType::Texture;
|
||||
prop.refCount = 1;
|
||||
m_properties.Insert(name, prop);
|
||||
EnsureTextureProperty(m_properties, name);
|
||||
|
||||
for (auto& binding : m_textureBindings) {
|
||||
if (binding.name == name) {
|
||||
binding.texture = texture;
|
||||
binding.texturePath = texture.Get() != nullptr ? texture->GetPath() : Containers::String();
|
||||
binding.pendingLoad.reset();
|
||||
MarkChanged(false);
|
||||
return;
|
||||
}
|
||||
@@ -289,6 +296,33 @@ void Material::SetTexture(const Containers::String& name, const ResourceHandle<T
|
||||
binding.name = name;
|
||||
binding.slot = static_cast<Core::uint32>(m_textureBindings.Size());
|
||||
binding.texture = texture;
|
||||
binding.texturePath = texture.Get() != nullptr ? texture->GetPath() : Containers::String();
|
||||
m_textureBindings.PushBack(binding);
|
||||
MarkChanged(false);
|
||||
}
|
||||
|
||||
void Material::SetTexturePath(const Containers::String& name, const Containers::String& texturePath) {
|
||||
if (texturePath.Empty()) {
|
||||
RemoveProperty(name);
|
||||
return;
|
||||
}
|
||||
|
||||
EnsureTextureProperty(m_properties, name);
|
||||
|
||||
for (auto& binding : m_textureBindings) {
|
||||
if (binding.name == name) {
|
||||
binding.texture.Reset();
|
||||
binding.texturePath = texturePath;
|
||||
binding.pendingLoad.reset();
|
||||
MarkChanged(false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
MaterialTextureBinding binding;
|
||||
binding.name = name;
|
||||
binding.slot = static_cast<Core::uint32>(m_textureBindings.Size());
|
||||
binding.texturePath = texturePath;
|
||||
m_textureBindings.PushBack(binding);
|
||||
MarkChanged(false);
|
||||
}
|
||||
@@ -343,8 +377,16 @@ bool Material::GetBool(const Containers::String& name) const {
|
||||
}
|
||||
|
||||
ResourceHandle<Texture> Material::GetTexture(const Containers::String& name) const {
|
||||
for (const auto& binding : m_textureBindings) {
|
||||
Material* material = const_cast<Material*>(this);
|
||||
material->ResolvePendingTextureBindings();
|
||||
for (Core::uint32 bindingIndex = 0; bindingIndex < material->m_textureBindings.Size(); ++bindingIndex) {
|
||||
MaterialTextureBinding& binding = material->m_textureBindings[bindingIndex];
|
||||
if (binding.name == name) {
|
||||
if (binding.texture.Get() == nullptr &&
|
||||
binding.pendingLoad == nullptr &&
|
||||
!binding.texturePath.Empty()) {
|
||||
material->BeginAsyncTextureLoad(bindingIndex);
|
||||
}
|
||||
return binding.texture;
|
||||
}
|
||||
}
|
||||
@@ -355,8 +397,24 @@ Containers::String Material::GetTextureBindingName(Core::uint32 index) const {
|
||||
return index < m_textureBindings.Size() ? m_textureBindings[index].name : Containers::String();
|
||||
}
|
||||
|
||||
Containers::String Material::GetTextureBindingPath(Core::uint32 index) const {
|
||||
return index < m_textureBindings.Size() ? m_textureBindings[index].texturePath : Containers::String();
|
||||
}
|
||||
|
||||
ResourceHandle<Texture> Material::GetTextureBindingTexture(Core::uint32 index) const {
|
||||
return index < m_textureBindings.Size() ? m_textureBindings[index].texture : ResourceHandle<Texture>();
|
||||
Material* material = const_cast<Material*>(this);
|
||||
material->ResolvePendingTextureBinding(index);
|
||||
if (index < material->m_textureBindings.Size()) {
|
||||
MaterialTextureBinding& binding = material->m_textureBindings[index];
|
||||
if (binding.texture.Get() == nullptr &&
|
||||
binding.pendingLoad == nullptr &&
|
||||
!binding.texturePath.Empty()) {
|
||||
material->BeginAsyncTextureLoad(index);
|
||||
}
|
||||
return binding.texture;
|
||||
}
|
||||
|
||||
return ResourceHandle<Texture>();
|
||||
}
|
||||
|
||||
std::vector<MaterialProperty> Material::GetProperties() const {
|
||||
@@ -404,6 +462,59 @@ void Material::RecalculateMemorySize() {
|
||||
UpdateMemorySize();
|
||||
}
|
||||
|
||||
void Material::BeginAsyncTextureLoad(Core::uint32 index) {
|
||||
if (index >= m_textureBindings.Size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MaterialTextureBinding& binding = m_textureBindings[index];
|
||||
if (binding.texture.Get() != nullptr || binding.texturePath.Empty() || binding.pendingLoad != nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
binding.pendingLoad = std::make_shared<PendingTextureLoadState>();
|
||||
std::weak_ptr<PendingTextureLoadState> weakState = binding.pendingLoad;
|
||||
const Containers::String texturePath = binding.texturePath;
|
||||
ResourceManager::Get().LoadAsync(
|
||||
texturePath,
|
||||
ResourceType::Texture,
|
||||
[weakState](LoadResult result) {
|
||||
if (std::shared_ptr<PendingTextureLoadState> state = weakState.lock()) {
|
||||
state->resource = result.resource;
|
||||
state->errorMessage = result.errorMessage;
|
||||
state->completed = true;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
void Material::ResolvePendingTextureBinding(Core::uint32 index) {
|
||||
if (index >= m_textureBindings.Size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
MaterialTextureBinding& binding = m_textureBindings[index];
|
||||
if (!binding.pendingLoad || !binding.pendingLoad->completed) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::shared_ptr<PendingTextureLoadState> completedLoad = std::move(binding.pendingLoad);
|
||||
binding.pendingLoad.reset();
|
||||
if (completedLoad->resource == nullptr) {
|
||||
return;
|
||||
}
|
||||
|
||||
binding.texture = ResourceHandle<Texture>(static_cast<Texture*>(completedLoad->resource));
|
||||
if (binding.texture.Get() != nullptr) {
|
||||
binding.texturePath = binding.texture->GetPath();
|
||||
}
|
||||
}
|
||||
|
||||
void Material::ResolvePendingTextureBindings() {
|
||||
for (Core::uint32 bindingIndex = 0; bindingIndex < m_textureBindings.Size(); ++bindingIndex) {
|
||||
ResolvePendingTextureBinding(bindingIndex);
|
||||
}
|
||||
}
|
||||
|
||||
bool Material::HasProperty(const Containers::String& name) const {
|
||||
return m_properties.Contains(name);
|
||||
}
|
||||
@@ -457,6 +568,7 @@ void Material::UpdateMemorySize() {
|
||||
|
||||
for (const auto& binding : m_textureBindings) {
|
||||
m_memorySize += binding.name.Length();
|
||||
m_memorySize += binding.texturePath.Length();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,12 +1,15 @@
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Core/Asset/ResourceTypes.h>
|
||||
#include <XCEngine/Resources/Shader/ShaderLoader.h>
|
||||
|
||||
#include <cctype>
|
||||
#include <cstring>
|
||||
#include <cstdlib>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -18,6 +21,107 @@ std::string ToStdString(const Containers::Array<Core::uint8>& data) {
|
||||
return std::string(reinterpret_cast<const char*>(data.Data()), data.Size());
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> ReadMaterialArtifactFileData(const Containers::String& path) {
|
||||
Containers::Array<Core::uint8> data;
|
||||
|
||||
auto tryRead = [&data](const std::filesystem::path& filePath, bool& opened) {
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
opened = false;
|
||||
return;
|
||||
}
|
||||
|
||||
opened = true;
|
||||
const std::streamsize size = file.tellg();
|
||||
if (size <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
file.seekg(0, std::ios::beg);
|
||||
data.Resize(static_cast<size_t>(size));
|
||||
if (!file.read(reinterpret_cast<char*>(data.Data()), size)) {
|
||||
data.Clear();
|
||||
}
|
||||
};
|
||||
|
||||
bool opened = false;
|
||||
const std::filesystem::path inputPath(path.CStr());
|
||||
tryRead(inputPath, opened);
|
||||
if (opened || path.Empty() || inputPath.is_absolute()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (resourceRoot.Empty()) {
|
||||
return data;
|
||||
}
|
||||
|
||||
tryRead(std::filesystem::path(resourceRoot.CStr()) / inputPath, opened);
|
||||
return data;
|
||||
}
|
||||
|
||||
Containers::String NormalizePathString(const std::filesystem::path& path) {
|
||||
return Containers::String(path.lexically_normal().generic_string().c_str());
|
||||
}
|
||||
|
||||
Containers::String ResolveArtifactDependencyPath(const Containers::String& dependencyPath,
|
||||
const Containers::String& ownerArtifactPath) {
|
||||
if (dependencyPath.Empty()) {
|
||||
return dependencyPath;
|
||||
}
|
||||
|
||||
std::filesystem::path dependencyFsPath(dependencyPath.CStr());
|
||||
if (dependencyFsPath.is_absolute() && std::filesystem::exists(dependencyFsPath)) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(dependencyFsPath)) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (!resourceRoot.Empty()) {
|
||||
const std::filesystem::path projectRelativeCandidate =
|
||||
std::filesystem::path(resourceRoot.CStr()) / dependencyFsPath;
|
||||
if (std::filesystem::exists(projectRelativeCandidate)) {
|
||||
return NormalizePathString(projectRelativeCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
const std::filesystem::path ownerArtifactFsPath(ownerArtifactPath.CStr());
|
||||
if (!ownerArtifactFsPath.is_absolute()) {
|
||||
return dependencyPath;
|
||||
}
|
||||
|
||||
const std::filesystem::path ownerRelativeCandidate =
|
||||
ownerArtifactFsPath.parent_path() / dependencyFsPath;
|
||||
if (std::filesystem::exists(ownerRelativeCandidate)) {
|
||||
return NormalizePathString(ownerRelativeCandidate);
|
||||
}
|
||||
|
||||
std::filesystem::path current = ownerArtifactFsPath.parent_path();
|
||||
while (!current.empty()) {
|
||||
if (current.filename() == "Library") {
|
||||
const std::filesystem::path projectRoot = current.parent_path();
|
||||
if (!projectRoot.empty()) {
|
||||
const std::filesystem::path projectRelativeCandidate = projectRoot / dependencyFsPath;
|
||||
if (std::filesystem::exists(projectRelativeCandidate)) {
|
||||
return NormalizePathString(projectRelativeCandidate);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const std::filesystem::path parent = current.parent_path();
|
||||
if (parent == current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
|
||||
return dependencyPath;
|
||||
}
|
||||
|
||||
size_t SkipWhitespace(const std::string& text, size_t pos) {
|
||||
while (pos < text.size() && std::isspace(static_cast<unsigned char>(text[pos])) != 0) {
|
||||
++pos;
|
||||
@@ -551,6 +655,181 @@ bool MaterialFileExists(const Containers::String& path) {
|
||||
return std::filesystem::exists(std::filesystem::path(resourceRoot.CStr()) / inputPath);
|
||||
}
|
||||
|
||||
ResourceHandle<Shader> LoadShaderHandle(const Containers::String& shaderPath);
|
||||
|
||||
template<typename T>
|
||||
bool ReadMaterialArtifactValue(const Containers::Array<Core::uint8>& data, size_t& offset, T& outValue) {
|
||||
if (offset + sizeof(T) > data.Size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::memcpy(&outValue, data.Data() + offset, sizeof(T));
|
||||
offset += sizeof(T);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadMaterialArtifactString(const Containers::Array<Core::uint8>& data,
|
||||
size_t& offset,
|
||||
Containers::String& outValue) {
|
||||
Core::uint32 length = 0;
|
||||
if (!ReadMaterialArtifactValue(data, offset, length)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (length == 0) {
|
||||
outValue.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
if (offset + length > data.Size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = Containers::String(
|
||||
std::string(reinterpret_cast<const char*>(data.Data() + offset), length).c_str());
|
||||
offset += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
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 LoadMaterialArtifact(const Containers::String& path) {
|
||||
const Containers::Array<Core::uint8> data = ReadMaterialArtifactFileData(path);
|
||||
if (data.Empty()) {
|
||||
return LoadResult("Failed to read material artifact: " + path);
|
||||
}
|
||||
|
||||
size_t offset = 0;
|
||||
MaterialArtifactFileHeader fileHeader;
|
||||
if (!ReadMaterialArtifactValue(data, offset, fileHeader)) {
|
||||
return LoadResult("Failed to parse material artifact header: " + path);
|
||||
}
|
||||
|
||||
const std::string magic(fileHeader.magic, fileHeader.magic + 7);
|
||||
if (magic != "XCMAT01") {
|
||||
return LoadResult("Invalid material artifact magic: " + path);
|
||||
}
|
||||
|
||||
auto material = std::make_unique<Material>();
|
||||
material->m_path = path;
|
||||
material->m_name = path;
|
||||
material->m_guid = ResourceGUID::Generate(path);
|
||||
|
||||
Containers::String materialName;
|
||||
Containers::String materialSourcePath;
|
||||
Containers::String shaderPath;
|
||||
Containers::String shaderPass;
|
||||
if (!ReadMaterialArtifactString(data, offset, materialName) ||
|
||||
!ReadMaterialArtifactString(data, offset, materialSourcePath) ||
|
||||
!ReadMaterialArtifactString(data, offset, shaderPath) ||
|
||||
!ReadMaterialArtifactString(data, offset, shaderPass)) {
|
||||
return LoadResult("Failed to parse material artifact strings: " + path);
|
||||
}
|
||||
|
||||
material->m_name = materialName.Empty() ? path : materialName;
|
||||
if (!materialSourcePath.Empty()) {
|
||||
material->m_path = materialSourcePath;
|
||||
material->m_guid = ResourceGUID::Generate(materialSourcePath);
|
||||
}
|
||||
|
||||
if (!shaderPath.Empty()) {
|
||||
const ResourceHandle<Shader> shaderHandle = LoadShaderHandle(shaderPath);
|
||||
if (shaderHandle.IsValid()) {
|
||||
material->SetShader(shaderHandle);
|
||||
}
|
||||
}
|
||||
if (!shaderPass.Empty()) {
|
||||
material->SetShaderPass(shaderPass);
|
||||
}
|
||||
|
||||
MaterialArtifactHeader header;
|
||||
if (!ReadMaterialArtifactValue(data, offset, header)) {
|
||||
return LoadResult("Failed to parse material artifact body: " + path);
|
||||
}
|
||||
|
||||
material->SetRenderQueue(header.renderQueue);
|
||||
material->SetRenderState(header.renderState);
|
||||
|
||||
for (Core::uint32 tagIndex = 0; tagIndex < header.tagCount; ++tagIndex) {
|
||||
Containers::String tagName;
|
||||
Containers::String tagValue;
|
||||
if (!ReadMaterialArtifactString(data, offset, tagName) ||
|
||||
!ReadMaterialArtifactString(data, offset, tagValue)) {
|
||||
return LoadResult("Failed to read material artifact tags: " + path);
|
||||
}
|
||||
|
||||
material->SetTag(tagName, tagValue);
|
||||
}
|
||||
|
||||
for (Core::uint32 propertyIndex = 0; propertyIndex < header.propertyCount; ++propertyIndex) {
|
||||
Containers::String propertyName;
|
||||
MaterialPropertyArtifact propertyArtifact;
|
||||
if (!ReadMaterialArtifactString(data, offset, propertyName) ||
|
||||
!ReadMaterialArtifactValue(data, offset, propertyArtifact)) {
|
||||
return LoadResult("Failed to read material artifact properties: " + path);
|
||||
}
|
||||
|
||||
MaterialProperty property;
|
||||
property.name = propertyName;
|
||||
property.type = static_cast<MaterialPropertyType>(propertyArtifact.propertyType);
|
||||
property.value = propertyArtifact.value;
|
||||
ApplyMaterialProperty(*material, property);
|
||||
}
|
||||
|
||||
for (Core::uint32 bindingIndex = 0; bindingIndex < header.textureBindingCount; ++bindingIndex) {
|
||||
Containers::String bindingName;
|
||||
Containers::String texturePath;
|
||||
if (!ReadMaterialArtifactString(data, offset, bindingName) ||
|
||||
!ReadMaterialArtifactString(data, offset, texturePath)) {
|
||||
return LoadResult("Failed to read material artifact texture bindings: " + path);
|
||||
}
|
||||
|
||||
if (!texturePath.Empty()) {
|
||||
material->SetTexturePath(bindingName, ResolveArtifactDependencyPath(texturePath, path));
|
||||
}
|
||||
}
|
||||
|
||||
material->m_isValid = true;
|
||||
material->RecalculateMemorySize();
|
||||
return LoadResult(material.release());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
MaterialLoader::MaterialLoader() = default;
|
||||
@@ -562,6 +841,7 @@ Containers::Array<Containers::String> MaterialLoader::GetSupportedExtensions() c
|
||||
extensions.PushBack("mat");
|
||||
extensions.PushBack("material");
|
||||
extensions.PushBack("json");
|
||||
extensions.PushBack("xcmat");
|
||||
return extensions;
|
||||
}
|
||||
|
||||
@@ -570,8 +850,8 @@ bool MaterialLoader::CanLoad(const Containers::String& path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path);
|
||||
return ext == "mat" || ext == "material" || ext == "json";
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
return ext == "mat" || ext == "material" || ext == "json" || ext == "xcmat";
|
||||
}
|
||||
|
||||
LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSettings* settings) {
|
||||
@@ -581,6 +861,11 @@ LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSett
|
||||
return CreateBuiltinMaterialResource(path);
|
||||
}
|
||||
|
||||
const Containers::String ext = GetExtension(path).ToLower();
|
||||
if (ext == "xcmat") {
|
||||
return LoadMaterialArtifact(path);
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> data = ReadFileData(path);
|
||||
Material* material = new Material();
|
||||
material->m_path = path;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
#include <XCEngine/Resources/Mesh/MeshLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Resources/Mesh/MeshImportSettings.h>
|
||||
#include <XCEngine/Resources/Material/Material.h>
|
||||
#include <XCEngine/Resources/Texture/Texture.h>
|
||||
@@ -148,6 +149,68 @@ Core::uint32 FindEmbeddedTextureIndex(const aiScene& scene, const aiTexture& emb
|
||||
return 0;
|
||||
}
|
||||
|
||||
Containers::String NormalizePathString(const std::filesystem::path& path) {
|
||||
return Containers::String(path.lexically_normal().generic_string().c_str());
|
||||
}
|
||||
|
||||
Containers::String ResolveArtifactDependencyPath(const Containers::String& dependencyPath,
|
||||
const Containers::String& ownerArtifactPath) {
|
||||
if (dependencyPath.Empty()) {
|
||||
return dependencyPath;
|
||||
}
|
||||
|
||||
std::filesystem::path dependencyFsPath(dependencyPath.CStr());
|
||||
if (dependencyFsPath.is_absolute() && std::filesystem::exists(dependencyFsPath)) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(dependencyFsPath)) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (!resourceRoot.Empty()) {
|
||||
const std::filesystem::path projectRelativeCandidate =
|
||||
std::filesystem::path(resourceRoot.CStr()) / dependencyFsPath;
|
||||
if (std::filesystem::exists(projectRelativeCandidate)) {
|
||||
return NormalizePathString(projectRelativeCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
const std::filesystem::path ownerArtifactFsPath(ownerArtifactPath.CStr());
|
||||
if (!ownerArtifactFsPath.is_absolute()) {
|
||||
return dependencyPath;
|
||||
}
|
||||
|
||||
const std::filesystem::path ownerRelativeCandidate =
|
||||
ownerArtifactFsPath.parent_path() / dependencyFsPath;
|
||||
if (std::filesystem::exists(ownerRelativeCandidate)) {
|
||||
return NormalizePathString(ownerRelativeCandidate);
|
||||
}
|
||||
|
||||
std::filesystem::path current = ownerArtifactFsPath.parent_path();
|
||||
while (!current.empty()) {
|
||||
if (current.filename() == "Library") {
|
||||
const std::filesystem::path projectRoot = current.parent_path();
|
||||
if (!projectRoot.empty()) {
|
||||
const std::filesystem::path projectRelativeCandidate = projectRoot / dependencyFsPath;
|
||||
if (std::filesystem::exists(projectRelativeCandidate)) {
|
||||
return NormalizePathString(projectRelativeCandidate);
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
const std::filesystem::path parent = current.parent_path();
|
||||
if (parent == current) {
|
||||
break;
|
||||
}
|
||||
current = parent;
|
||||
}
|
||||
|
||||
return dependencyPath;
|
||||
}
|
||||
|
||||
Texture* CreateRawTexture(const Containers::String& texturePath,
|
||||
TextureFormat format,
|
||||
Core::uint32 width,
|
||||
@@ -570,7 +633,7 @@ LoadResult LoadMeshArtifact(const Containers::String& path) {
|
||||
}
|
||||
|
||||
const std::string magic(header.magic, header.magic + 7);
|
||||
if (magic != "XCMESH1") {
|
||||
if (magic != "XCMESH2") {
|
||||
return LoadResult(Containers::String("Invalid mesh artifact magic: ") + path);
|
||||
}
|
||||
|
||||
@@ -628,93 +691,31 @@ LoadResult LoadMeshArtifact(const Containers::String& path) {
|
||||
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();
|
||||
MaterialLoader materialLoader;
|
||||
|
||||
for (Core::uint32 materialIndex = 0; materialIndex < header.materialCount; ++materialIndex) {
|
||||
Core::uint32 materialPresent = 0;
|
||||
input.read(reinterpret_cast<char*>(&materialPresent), sizeof(materialPresent));
|
||||
const Containers::String materialArtifactPath = ReadBinaryString(input);
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read mesh material flag: ") + path);
|
||||
return LoadResult(Containers::String("Failed to read mesh material artifact path: ") + path);
|
||||
}
|
||||
|
||||
if (materialPresent == 0) {
|
||||
if (materialArtifactPath.Empty()) {
|
||||
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));
|
||||
}
|
||||
const Containers::String resolvedMaterialArtifactPath =
|
||||
ResolveArtifactDependencyPath(materialArtifactPath, path);
|
||||
LoadResult materialResult = materialLoader.Load(resolvedMaterialArtifactPath);
|
||||
if (!materialResult || materialResult.resource == nullptr) {
|
||||
return LoadResult(
|
||||
Containers::String("Failed to load mesh material artifact: ") +
|
||||
resolvedMaterialArtifactPath +
|
||||
" for " +
|
||||
path);
|
||||
}
|
||||
|
||||
auto* material = static_cast<Material*>(materialResult.resource);
|
||||
material->RecalculateMemorySize();
|
||||
mesh->AddMaterial(material);
|
||||
}
|
||||
|
||||
@@ -51,7 +51,15 @@ LoadResult CreateTextureResource(const Containers::String& path,
|
||||
}
|
||||
|
||||
LoadResult LoadTextureArtifact(const Containers::String& path) {
|
||||
std::ifstream input(path.CStr(), std::ios::binary);
|
||||
std::filesystem::path resolvedPath(path.CStr());
|
||||
if (!resolvedPath.is_absolute() && !std::filesystem::exists(resolvedPath)) {
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (!resourceRoot.Empty()) {
|
||||
resolvedPath = std::filesystem::path(resourceRoot.CStr()) / resolvedPath;
|
||||
}
|
||||
}
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return LoadResult(Containers::String("Failed to read texture artifact: ") + path);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user