Split mesh artifacts into material and texture artifacts

This commit is contained in:
2026-04-02 19:36:16 +08:00
parent b2d0570b1b
commit e30f5d5ffa
12 changed files with 939 additions and 135 deletions

View File

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