chore: checkpoint current workspace changes
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
#include <XCEngine/Resources/GaussianSplat/GaussianSplatArtifactIO.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/GaussianSplat/GaussianSplat.h>
|
||||
@@ -35,6 +36,45 @@ std::filesystem::path ResolveArtifactPath(const Containers::String& path) {
|
||||
return resolvedPath.lexically_normal();
|
||||
}
|
||||
|
||||
bool ReadArtifactFileData(const Containers::String& path,
|
||||
ResourceType resourceType,
|
||||
Containers::Array<Core::uint8>& outData) {
|
||||
outData.Clear();
|
||||
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(path);
|
||||
Containers::String containerError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(resolvedPath.generic_string().c_str()),
|
||||
resourceType,
|
||||
outData,
|
||||
&containerError)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary | std::ios::ate);
|
||||
if (!input.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = input.tellg();
|
||||
if (size < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
input.seekg(0, std::ios::beg);
|
||||
outData.Resize(static_cast<size_t>(size));
|
||||
if (size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!input.read(reinterpret_cast<char*>(outData.Data()), size)) {
|
||||
outData.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LoadResult CreateOwnedGaussianSplatResource(const Containers::String& path,
|
||||
const GaussianSplatMetadata& metadata,
|
||||
Containers::Array<GaussianSplatSection>&& sections,
|
||||
@@ -58,6 +98,67 @@ LoadResult CreateOwnedGaussianSplatResource(const Containers::String& path,
|
||||
|
||||
} // namespace
|
||||
|
||||
bool SerializeGaussianSplatArtifactPayload(const GaussianSplat& gaussianSplat,
|
||||
Containers::Array<Core::uint8>& outPayload,
|
||||
Containers::String* outErrorMessage) {
|
||||
(void)outErrorMessage;
|
||||
|
||||
GaussianSplatArtifactFileHeader fileHeader;
|
||||
|
||||
const GaussianSplatMetadata& metadata = gaussianSplat.GetMetadata();
|
||||
GaussianSplatArtifactHeader header;
|
||||
header.contentVersion = metadata.contentVersion;
|
||||
header.splatCount = metadata.splatCount;
|
||||
header.chunkCount = metadata.chunkCount;
|
||||
header.cameraCount = metadata.cameraCount;
|
||||
header.boundsMin = metadata.bounds.GetMin();
|
||||
header.boundsMax = metadata.bounds.GetMax();
|
||||
header.positionFormat = static_cast<Core::uint32>(metadata.positionFormat);
|
||||
header.otherFormat = static_cast<Core::uint32>(metadata.otherFormat);
|
||||
header.colorFormat = static_cast<Core::uint32>(metadata.colorFormat);
|
||||
header.shFormat = static_cast<Core::uint32>(metadata.shFormat);
|
||||
header.chunkFormat = static_cast<Core::uint32>(metadata.chunkFormat);
|
||||
header.cameraFormat = static_cast<Core::uint32>(metadata.cameraFormat);
|
||||
header.sectionCount = static_cast<Core::uint32>(gaussianSplat.GetSections().Size());
|
||||
header.payloadSize = static_cast<Core::uint64>(gaussianSplat.GetPayloadSize());
|
||||
|
||||
const size_t sectionTableSize =
|
||||
gaussianSplat.GetSections().Size() * sizeof(GaussianSplatArtifactSectionRecord);
|
||||
const size_t totalSize =
|
||||
sizeof(fileHeader) +
|
||||
sizeof(header) +
|
||||
sectionTableSize +
|
||||
gaussianSplat.GetPayloadSize();
|
||||
|
||||
outPayload.Resize(totalSize);
|
||||
size_t writeOffset = 0;
|
||||
auto appendBytes = [&outPayload, &writeOffset](const void* source, size_t byteCount) {
|
||||
if (byteCount == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::memcpy(outPayload.Data() + writeOffset, source, byteCount);
|
||||
writeOffset += byteCount;
|
||||
};
|
||||
|
||||
appendBytes(&fileHeader, sizeof(fileHeader));
|
||||
appendBytes(&header, sizeof(header));
|
||||
|
||||
for (const GaussianSplatSection& section : gaussianSplat.GetSections()) {
|
||||
GaussianSplatArtifactSectionRecord sectionRecord;
|
||||
sectionRecord.sectionType = static_cast<Core::uint32>(section.type);
|
||||
sectionRecord.format = static_cast<Core::uint32>(section.format);
|
||||
sectionRecord.payloadOffset = section.dataOffset;
|
||||
sectionRecord.dataSize = section.dataSize;
|
||||
sectionRecord.elementCount = section.elementCount;
|
||||
sectionRecord.elementStride = section.elementStride;
|
||||
appendBytes(§ionRecord, sizeof(sectionRecord));
|
||||
}
|
||||
|
||||
appendBytes(gaussianSplat.GetPayloadData(), gaussianSplat.GetPayloadSize());
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteGaussianSplatArtifactFile(const Containers::String& artifactPath,
|
||||
const GaussianSplat& gaussianSplat,
|
||||
Containers::String* outErrorMessage) {
|
||||
@@ -76,6 +177,11 @@ bool WriteGaussianSplatArtifactFile(const Containers::String& artifactPath,
|
||||
}
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
if (!SerializeGaussianSplatArtifactPayload(gaussianSplat, payload, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream output(resolvedPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
@@ -84,40 +190,8 @@ bool WriteGaussianSplatArtifactFile(const Containers::String& artifactPath,
|
||||
return false;
|
||||
}
|
||||
|
||||
GaussianSplatArtifactFileHeader fileHeader;
|
||||
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
||||
|
||||
const GaussianSplatMetadata& metadata = gaussianSplat.GetMetadata();
|
||||
GaussianSplatArtifactHeader header;
|
||||
header.contentVersion = metadata.contentVersion;
|
||||
header.splatCount = metadata.splatCount;
|
||||
header.chunkCount = metadata.chunkCount;
|
||||
header.cameraCount = metadata.cameraCount;
|
||||
header.boundsMin = metadata.bounds.GetMin();
|
||||
header.boundsMax = metadata.bounds.GetMax();
|
||||
header.positionFormat = static_cast<Core::uint32>(metadata.positionFormat);
|
||||
header.otherFormat = static_cast<Core::uint32>(metadata.otherFormat);
|
||||
header.colorFormat = static_cast<Core::uint32>(metadata.colorFormat);
|
||||
header.shFormat = static_cast<Core::uint32>(metadata.shFormat);
|
||||
header.chunkFormat = static_cast<Core::uint32>(metadata.chunkFormat);
|
||||
header.cameraFormat = static_cast<Core::uint32>(metadata.cameraFormat);
|
||||
header.sectionCount = static_cast<Core::uint32>(gaussianSplat.GetSections().Size());
|
||||
header.payloadSize = static_cast<Core::uint64>(gaussianSplat.GetPayloadSize());
|
||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
|
||||
for (const GaussianSplatSection& section : gaussianSplat.GetSections()) {
|
||||
GaussianSplatArtifactSectionRecord sectionRecord;
|
||||
sectionRecord.sectionType = static_cast<Core::uint32>(section.type);
|
||||
sectionRecord.format = static_cast<Core::uint32>(section.format);
|
||||
sectionRecord.payloadOffset = section.dataOffset;
|
||||
sectionRecord.dataSize = section.dataSize;
|
||||
sectionRecord.elementCount = section.elementCount;
|
||||
sectionRecord.elementStride = section.elementStride;
|
||||
output.write(reinterpret_cast<const char*>(§ionRecord), sizeof(sectionRecord));
|
||||
}
|
||||
|
||||
if (gaussianSplat.GetPayloadSize() > 0) {
|
||||
output.write(reinterpret_cast<const char*>(gaussianSplat.GetPayloadData()), gaussianSplat.GetPayloadSize());
|
||||
if (!payload.Empty()) {
|
||||
output.write(reinterpret_cast<const char*>(payload.Data()), static_cast<std::streamsize>(payload.Size()));
|
||||
}
|
||||
|
||||
if (!output && outErrorMessage != nullptr) {
|
||||
@@ -128,16 +202,26 @@ bool WriteGaussianSplatArtifactFile(const Containers::String& artifactPath,
|
||||
}
|
||||
|
||||
LoadResult LoadGaussianSplatArtifact(const Containers::String& path) {
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(path);
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
Containers::Array<Core::uint8> data;
|
||||
if (!ReadArtifactFileData(path, ResourceType::GaussianSplat, data)) {
|
||||
return LoadResult(Containers::String("Failed to read GaussianSplat artifact: ") + path);
|
||||
}
|
||||
|
||||
size_t offset = 0;
|
||||
auto readBytes = [&data, &offset](void* destination, size_t byteCount) -> bool {
|
||||
if (offset + byteCount > data.Size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (byteCount > 0) {
|
||||
std::memcpy(destination, data.Data() + offset, byteCount);
|
||||
offset += byteCount;
|
||||
}
|
||||
return true;
|
||||
};
|
||||
|
||||
GaussianSplatArtifactFileHeader fileHeader;
|
||||
input.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
|
||||
if (!input) {
|
||||
if (!readBytes(&fileHeader, sizeof(fileHeader))) {
|
||||
return LoadResult(Containers::String("Failed to parse GaussianSplat artifact file header: ") + path);
|
||||
}
|
||||
|
||||
@@ -149,8 +233,7 @@ LoadResult LoadGaussianSplatArtifact(const Containers::String& path) {
|
||||
}
|
||||
|
||||
GaussianSplatArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
if (!readBytes(&header, sizeof(header))) {
|
||||
return LoadResult(Containers::String("Failed to parse GaussianSplat artifact header: ") + path);
|
||||
}
|
||||
|
||||
@@ -158,8 +241,7 @@ LoadResult LoadGaussianSplatArtifact(const Containers::String& path) {
|
||||
sections.Reserve(header.sectionCount);
|
||||
for (Core::uint32 index = 0; index < header.sectionCount; ++index) {
|
||||
GaussianSplatArtifactSectionRecord sectionRecord;
|
||||
input.read(reinterpret_cast<char*>(§ionRecord), sizeof(sectionRecord));
|
||||
if (!input) {
|
||||
if (!readBytes(§ionRecord, sizeof(sectionRecord))) {
|
||||
return LoadResult(Containers::String("Failed to read GaussianSplat artifact section table: ") + path);
|
||||
}
|
||||
|
||||
@@ -175,11 +257,13 @@ LoadResult LoadGaussianSplatArtifact(const Containers::String& path) {
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
payload.Resize(static_cast<size_t>(header.payloadSize));
|
||||
if (header.payloadSize > 0) {
|
||||
input.read(reinterpret_cast<char*>(payload.Data()), static_cast<std::streamsize>(header.payloadSize));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read GaussianSplat artifact payload: ") + path);
|
||||
}
|
||||
const size_t remainingBytes = data.Size() - offset;
|
||||
if (header.payloadSize > static_cast<Core::uint64>(remainingBytes)) {
|
||||
return LoadResult(Containers::String("Failed to read GaussianSplat artifact payload: ") + path);
|
||||
}
|
||||
if (header.payloadSize > 0 &&
|
||||
!readBytes(payload.Data(), static_cast<size_t>(header.payloadSize))) {
|
||||
return LoadResult(Containers::String("Failed to read GaussianSplat artifact payload: ") + path);
|
||||
}
|
||||
|
||||
GaussianSplatMetadata metadata;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <XCEngine/Resources/Material/MaterialLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
@@ -19,6 +20,32 @@ namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
Containers::String GetPathExtensionString(const Containers::String& path) {
|
||||
const std::filesystem::path fsPath(path.CStr());
|
||||
const std::string extension = fsPath.has_extension()
|
||||
? fsPath.extension().string()
|
||||
: std::string();
|
||||
if (!extension.empty() && extension[0] == '.') {
|
||||
return Containers::String(extension.substr(1).c_str());
|
||||
}
|
||||
|
||||
return Containers::String(extension.c_str());
|
||||
}
|
||||
|
||||
Containers::String GetMaterialPathExtension(const Containers::String& path) {
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
const Containers::String entryExtension = GetPathExtensionString(entryName);
|
||||
if (!entryExtension.Empty()) {
|
||||
return entryExtension;
|
||||
}
|
||||
return GetPathExtensionString(containerPath);
|
||||
}
|
||||
|
||||
return GetPathExtensionString(path);
|
||||
}
|
||||
|
||||
std::string ToStdString(const Containers::Array<Core::uint8>& data) {
|
||||
return std::string(reinterpret_cast<const char*>(data.Data()), data.Size());
|
||||
}
|
||||
@@ -27,6 +54,17 @@ Containers::Array<Core::uint8> ReadMaterialArtifactFileData(const Containers::St
|
||||
Containers::Array<Core::uint8> data;
|
||||
|
||||
auto tryRead = [&data](const std::filesystem::path& filePath, bool& opened) {
|
||||
opened = false;
|
||||
Containers::String containerError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(filePath.generic_string().c_str()),
|
||||
ResourceType::Material,
|
||||
data,
|
||||
&containerError)) {
|
||||
opened = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
opened = false;
|
||||
@@ -143,12 +181,27 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
|
||||
}
|
||||
|
||||
std::filesystem::path dependencyFsPath(dependencyPath.CStr());
|
||||
Containers::String containerPathText;
|
||||
Containers::String entryName;
|
||||
const bool isContainerEntryPath =
|
||||
TryParseArtifactContainerEntryPath(dependencyPath, containerPathText, entryName);
|
||||
if (isContainerEntryPath) {
|
||||
dependencyFsPath = std::filesystem::path(containerPathText.CStr());
|
||||
}
|
||||
|
||||
auto rebuildResolvedPath = [&entryName, isContainerEntryPath](const std::filesystem::path& resolvedPath) {
|
||||
const Containers::String normalizedPath = NormalizePathString(resolvedPath);
|
||||
return isContainerEntryPath
|
||||
? BuildArtifactContainerEntryPath(normalizedPath, entryName)
|
||||
: normalizedPath;
|
||||
};
|
||||
|
||||
if (dependencyFsPath.is_absolute() && std::filesystem::exists(dependencyFsPath)) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
return rebuildResolvedPath(dependencyFsPath);
|
||||
}
|
||||
|
||||
if (std::filesystem::exists(dependencyFsPath)) {
|
||||
return NormalizePathString(dependencyFsPath);
|
||||
return rebuildResolvedPath(dependencyFsPath);
|
||||
}
|
||||
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
@@ -156,7 +209,7 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
|
||||
const std::filesystem::path projectRelativeCandidate =
|
||||
std::filesystem::path(resourceRoot.CStr()) / dependencyFsPath;
|
||||
if (std::filesystem::exists(projectRelativeCandidate)) {
|
||||
return NormalizePathString(projectRelativeCandidate);
|
||||
return rebuildResolvedPath(projectRelativeCandidate);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -168,7 +221,7 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
|
||||
const std::filesystem::path ownerRelativeCandidate =
|
||||
ownerArtifactFsPath.parent_path() / dependencyFsPath;
|
||||
if (std::filesystem::exists(ownerRelativeCandidate)) {
|
||||
return NormalizePathString(ownerRelativeCandidate);
|
||||
return rebuildResolvedPath(ownerRelativeCandidate);
|
||||
}
|
||||
|
||||
std::filesystem::path current = ownerArtifactFsPath.parent_path();
|
||||
@@ -178,7 +231,7 @@ Containers::String ResolveArtifactDependencyPath(const Containers::String& depen
|
||||
if (!projectRoot.empty()) {
|
||||
const std::filesystem::path projectRelativeCandidate = projectRoot / dependencyFsPath;
|
||||
if (std::filesystem::exists(projectRelativeCandidate)) {
|
||||
return NormalizePathString(projectRelativeCandidate);
|
||||
return rebuildResolvedPath(projectRelativeCandidate);
|
||||
}
|
||||
}
|
||||
break;
|
||||
@@ -1800,7 +1853,7 @@ bool MaterialLoader::CanLoad(const Containers::String& path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
const Containers::String ext = GetMaterialPathExtension(path).ToLower();
|
||||
return ext == "mat" || ext == "material" || ext == "json" || ext == "xcmat";
|
||||
}
|
||||
|
||||
@@ -1811,7 +1864,7 @@ LoadResult MaterialLoader::Load(const Containers::String& path, const ImportSett
|
||||
return CreateBuiltinMaterialResource(path);
|
||||
}
|
||||
|
||||
const Containers::String ext = GetExtension(path).ToLower();
|
||||
const Containers::String ext = GetMaterialPathExtension(path).ToLower();
|
||||
if (ext == "xcmat") {
|
||||
return LoadMaterialArtifact(path);
|
||||
}
|
||||
|
||||
@@ -32,6 +32,32 @@ namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
Containers::String GetPathExtensionString(const Containers::String& path) {
|
||||
const std::filesystem::path fsPath(path.CStr());
|
||||
const std::string extension = fsPath.has_extension()
|
||||
? fsPath.extension().string()
|
||||
: std::string();
|
||||
if (!extension.empty() && extension[0] == '.') {
|
||||
return Containers::String(extension.substr(1).c_str());
|
||||
}
|
||||
|
||||
return Containers::String(extension.c_str());
|
||||
}
|
||||
|
||||
Containers::String GetMeshPathExtension(const Containers::String& path) {
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
const Containers::String entryExtension = GetPathExtensionString(entryName);
|
||||
if (!entryExtension.Empty()) {
|
||||
return entryExtension;
|
||||
}
|
||||
return GetPathExtensionString(containerPath);
|
||||
}
|
||||
|
||||
return GetPathExtensionString(path);
|
||||
}
|
||||
|
||||
struct ImportedMeshData {
|
||||
std::vector<StaticMeshVertex> vertices;
|
||||
std::vector<Core::uint32> indices;
|
||||
@@ -1074,7 +1100,7 @@ bool MeshLoader::CanLoad(const Containers::String& path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
const Containers::String ext = GetMeshPathExtension(path).ToLower();
|
||||
|
||||
return ext == "fbx" || ext == "obj" || ext == "gltf" ||
|
||||
ext == "glb" || ext == "dae" || ext == "stl" ||
|
||||
@@ -1086,7 +1112,7 @@ LoadResult MeshLoader::Load(const Containers::String& path, const ImportSettings
|
||||
return CreateBuiltinMeshResource(path);
|
||||
}
|
||||
|
||||
const Containers::String ext = GetExtension(path).ToLower();
|
||||
const Containers::String ext = GetMeshPathExtension(path).ToLower();
|
||||
|
||||
if (!CanLoad(path)) {
|
||||
return LoadResult(Containers::String("Unsupported mesh format: ") + ext);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <XCEngine/Resources/Model/ModelArtifactIO.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Model/Model.h>
|
||||
@@ -7,6 +8,7 @@
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
|
||||
namespace XCEngine {
|
||||
@@ -35,7 +37,46 @@ std::filesystem::path ResolveArtifactPath(const Containers::String& path) {
|
||||
return resolvedPath.lexically_normal();
|
||||
}
|
||||
|
||||
void WriteString(std::ofstream& stream, const Containers::String& value) {
|
||||
bool ReadArtifactFileData(const Containers::String& path,
|
||||
ResourceType resourceType,
|
||||
Containers::Array<Core::uint8>& outData) {
|
||||
outData.Clear();
|
||||
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(path);
|
||||
Containers::String containerError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(resolvedPath.generic_string().c_str()),
|
||||
resourceType,
|
||||
outData,
|
||||
&containerError)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary | std::ios::ate);
|
||||
if (!input.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = input.tellg();
|
||||
if (size < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
input.seekg(0, std::ios::beg);
|
||||
outData.Resize(static_cast<size_t>(size));
|
||||
if (size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!input.read(reinterpret_cast<char*>(outData.Data()), size)) {
|
||||
outData.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void WriteString(std::ostream& stream, const Containers::String& value) {
|
||||
const Core::uint32 length = static_cast<Core::uint32>(value.Length());
|
||||
stream.write(reinterpret_cast<const char*>(&length), sizeof(length));
|
||||
if (length > 0) {
|
||||
@@ -43,20 +84,65 @@ void WriteString(std::ofstream& stream, const Containers::String& value) {
|
||||
}
|
||||
}
|
||||
|
||||
Containers::String ReadString(std::ifstream& stream) {
|
||||
bool ReadString(const Containers::Array<Core::uint8>& data,
|
||||
size_t& offset,
|
||||
Containers::String& outValue) {
|
||||
Core::uint32 length = 0;
|
||||
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
|
||||
if (!stream || length == 0) {
|
||||
return Containers::String();
|
||||
if (offset + sizeof(length) > data.Size()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::string buffer(length, '\0');
|
||||
stream.read(buffer.data(), length);
|
||||
if (!stream) {
|
||||
return Containers::String();
|
||||
std::memcpy(&length, data.Data() + offset, sizeof(length));
|
||||
offset += sizeof(length);
|
||||
if (length == 0) {
|
||||
outValue.Clear();
|
||||
return true;
|
||||
}
|
||||
|
||||
return Containers::String(buffer.c_str());
|
||||
if (offset + length > data.Size()) {
|
||||
outValue.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
outValue = Containers::String(reinterpret_cast<const char*>(data.Data() + offset), length);
|
||||
offset += length;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
bool ReadValue(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;
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> ToByteArray(const std::string& text) {
|
||||
Containers::Array<Core::uint8> bytes;
|
||||
if (text.empty()) {
|
||||
return bytes;
|
||||
}
|
||||
|
||||
bytes.ResizeUninitialized(text.size());
|
||||
std::memcpy(bytes.Data(), text.data(), text.size());
|
||||
return bytes;
|
||||
}
|
||||
|
||||
ModelArtifactHeader BuildModelArtifactHeader(const Model& model) {
|
||||
ModelArtifactHeader header;
|
||||
header.nodeCount = static_cast<Core::uint32>(model.GetNodes().Size());
|
||||
header.meshBindingCount = static_cast<Core::uint32>(model.GetMeshBindings().Size());
|
||||
header.materialBindingCount = static_cast<Core::uint32>(model.GetMaterialBindings().Size());
|
||||
header.rootNodeIndex = model.GetRootNodeIndex();
|
||||
return header;
|
||||
}
|
||||
|
||||
bool ValidateModelArtifactHeader(const ModelArtifactHeader& header) {
|
||||
return header.rootNodeIndex == kInvalidModelNodeIndex ||
|
||||
header.rootNodeIndex < header.nodeCount;
|
||||
}
|
||||
|
||||
LoadResult CreateOwnedModelResource(const Containers::String& path,
|
||||
@@ -94,40 +180,17 @@ LoadResult CreateOwnedModelResource(const Containers::String& path,
|
||||
|
||||
} // namespace
|
||||
|
||||
bool WriteModelArtifactFile(const Containers::String& artifactPath,
|
||||
const Model& model,
|
||||
Containers::String* outErrorMessage) {
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(artifactPath);
|
||||
std::error_code ec;
|
||||
const std::filesystem::path parentPath = resolvedPath.parent_path();
|
||||
if (!parentPath.empty()) {
|
||||
std::filesystem::create_directories(parentPath, ec);
|
||||
if (ec) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage =
|
||||
Containers::String("Failed to create model artifact directory: ") +
|
||||
Containers::String(parentPath.generic_string().c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
bool SerializeModelArtifactPayload(const Model& model,
|
||||
Containers::Array<Core::uint8>& outPayload,
|
||||
Containers::String* outErrorMessage) {
|
||||
(void)outErrorMessage;
|
||||
|
||||
std::ofstream output(resolvedPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to open model artifact for write: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
std::ostringstream output(std::ios::binary | std::ios::out);
|
||||
|
||||
ModelArtifactFileHeader fileHeader;
|
||||
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
||||
|
||||
ModelArtifactHeader header;
|
||||
header.nodeCount = static_cast<Core::uint32>(model.GetNodes().Size());
|
||||
header.meshBindingCount = static_cast<Core::uint32>(model.GetMeshBindings().Size());
|
||||
header.materialBindingCount = static_cast<Core::uint32>(model.GetMaterialBindings().Size());
|
||||
header.rootNodeIndex = model.GetRootNodeIndex();
|
||||
const ModelArtifactHeader header = BuildModelArtifactHeader(model);
|
||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
|
||||
for (const ModelNode& node : model.GetNodes()) {
|
||||
@@ -158,27 +221,25 @@ bool WriteModelArtifactFile(const Containers::String& artifactPath,
|
||||
output.write(reinterpret_cast<const char*>(&bindingArtifact), sizeof(bindingArtifact));
|
||||
}
|
||||
|
||||
if (!output && outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write model artifact: ") + artifactPath;
|
||||
if (!output) {
|
||||
outPayload.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return static_cast<bool>(output);
|
||||
outPayload = ToByteArray(output.str());
|
||||
return true;
|
||||
}
|
||||
|
||||
LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(path);
|
||||
namespace {
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
return LoadResult(Containers::String("Failed to read model artifact: ") + path);
|
||||
}
|
||||
LoadResult ParseModelArtifactPayload(const Containers::String& path,
|
||||
const Containers::Array<Core::uint8>& data) {
|
||||
size_t offset = 0;
|
||||
|
||||
ModelArtifactFileHeader fileHeader;
|
||||
input.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
|
||||
if (!input) {
|
||||
if (!ReadValue(data, offset, fileHeader)) {
|
||||
return LoadResult(Containers::String("Failed to parse model artifact file header: ") + path);
|
||||
}
|
||||
|
||||
const bool validFileHeader =
|
||||
std::memcmp(fileHeader.magic, "XCMOD01", 7) == 0 &&
|
||||
fileHeader.schemaVersion == kModelArtifactSchemaVersion;
|
||||
@@ -187,13 +248,11 @@ LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
}
|
||||
|
||||
ModelArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
if (!ReadValue(data, offset, header)) {
|
||||
return LoadResult(Containers::String("Failed to parse model artifact header: ") + path);
|
||||
}
|
||||
|
||||
if (header.rootNodeIndex != kInvalidModelNodeIndex &&
|
||||
header.rootNodeIndex >= header.nodeCount) {
|
||||
if (!ValidateModelArtifactHeader(header)) {
|
||||
return LoadResult(Containers::String("Invalid model artifact root node index: ") + path);
|
||||
}
|
||||
|
||||
@@ -201,11 +260,12 @@ LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
nodes.Reserve(header.nodeCount);
|
||||
for (Core::uint32 index = 0; index < header.nodeCount; ++index) {
|
||||
ModelNode node;
|
||||
node.name = ReadString(input);
|
||||
if (!ReadString(data, offset, node.name)) {
|
||||
return LoadResult(Containers::String("Failed to read model node artifact name: ") + path);
|
||||
}
|
||||
|
||||
ModelNodeArtifactHeader nodeHeader;
|
||||
input.read(reinterpret_cast<char*>(&nodeHeader), sizeof(nodeHeader));
|
||||
if (!input) {
|
||||
if (!ReadValue(data, offset, nodeHeader)) {
|
||||
return LoadResult(Containers::String("Failed to read model node artifact: ") + path);
|
||||
}
|
||||
|
||||
@@ -222,8 +282,7 @@ LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
meshBindings.Reserve(header.meshBindingCount);
|
||||
for (Core::uint32 index = 0; index < header.meshBindingCount; ++index) {
|
||||
ModelMeshBindingArtifact bindingArtifact;
|
||||
input.read(reinterpret_cast<char*>(&bindingArtifact), sizeof(bindingArtifact));
|
||||
if (!input) {
|
||||
if (!ReadValue(data, offset, bindingArtifact)) {
|
||||
return LoadResult(Containers::String("Failed to read model mesh binding artifact: ") + path);
|
||||
}
|
||||
|
||||
@@ -238,8 +297,7 @@ LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
materialBindings.Reserve(header.materialBindingCount);
|
||||
for (Core::uint32 index = 0; index < header.materialBindingCount; ++index) {
|
||||
ModelMaterialBindingArtifact bindingArtifact;
|
||||
input.read(reinterpret_cast<char*>(&bindingArtifact), sizeof(bindingArtifact));
|
||||
if (!input) {
|
||||
if (!ReadValue(data, offset, bindingArtifact)) {
|
||||
return LoadResult(Containers::String("Failed to read model material binding artifact: ") + path);
|
||||
}
|
||||
|
||||
@@ -257,5 +315,57 @@ LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
std::move(materialBindings));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
bool WriteModelArtifactFile(const Containers::String& artifactPath,
|
||||
const Model& model,
|
||||
Containers::String* outErrorMessage) {
|
||||
const std::filesystem::path resolvedPath = ResolveArtifactPath(artifactPath);
|
||||
std::error_code ec;
|
||||
const std::filesystem::path parentPath = resolvedPath.parent_path();
|
||||
if (!parentPath.empty()) {
|
||||
std::filesystem::create_directories(parentPath, ec);
|
||||
if (ec) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage =
|
||||
Containers::String("Failed to create model artifact directory: ") +
|
||||
Containers::String(parentPath.generic_string().c_str());
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
if (!SerializeModelArtifactPayload(model, payload, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream output(resolvedPath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to open model artifact for write: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!payload.Empty()) {
|
||||
output.write(reinterpret_cast<const char*>(payload.Data()), static_cast<std::streamsize>(payload.Size()));
|
||||
}
|
||||
|
||||
if (!output && outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write model artifact: ") + artifactPath;
|
||||
}
|
||||
return static_cast<bool>(output);
|
||||
}
|
||||
|
||||
LoadResult LoadModelArtifact(const Containers::String& path) {
|
||||
Containers::Array<Core::uint8> data;
|
||||
if (!ReadArtifactFileData(path, ResourceType::Model, data)) {
|
||||
return LoadResult(Containers::String("Failed to read model artifact: ") + path);
|
||||
}
|
||||
|
||||
return ParseModelArtifactPayload(path, data);
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include "ShaderFileUtils.h"
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
|
||||
#include <filesystem>
|
||||
@@ -15,6 +16,16 @@ Containers::Array<Core::uint8> TryReadFileData(
|
||||
bool& opened) {
|
||||
Containers::Array<Core::uint8> data;
|
||||
|
||||
Containers::String payloadError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(filePath.generic_string().c_str()),
|
||||
ResourceType::Shader,
|
||||
data,
|
||||
&payloadError)) {
|
||||
opened = true;
|
||||
return data;
|
||||
}
|
||||
|
||||
std::ifstream file(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!file.is_open()) {
|
||||
opened = false;
|
||||
@@ -69,9 +80,16 @@ bool ReadShaderTextFile(const Containers::String& path, Containers::String& outT
|
||||
}
|
||||
|
||||
Containers::String GetShaderPathExtension(const Containers::String& path) {
|
||||
Containers::String normalizedPath = path;
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
normalizedPath = containerPath;
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
@@ -81,7 +99,7 @@ Containers::String GetShaderPathExtension(const Containers::String& path) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
return path.Substring(dotPos + 1);
|
||||
return normalizedPath.Substring(dotPos + 1);
|
||||
}
|
||||
|
||||
ShaderType DetectShaderTypeFromPath(const Containers::String& path) {
|
||||
|
||||
388
engine/src/Resources/Shader/ShaderCompilationCache.cpp
Normal file
388
engine/src/Resources/Shader/ShaderCompilationCache.cpp
Normal file
@@ -0,0 +1,388 @@
|
||||
#include <XCEngine/Resources/Shader/ShaderCompilationCache.h>
|
||||
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
#include <sstream>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
namespace XCEngine {
|
||||
namespace Resources {
|
||||
|
||||
namespace fs = std::filesystem;
|
||||
|
||||
namespace {
|
||||
|
||||
struct ShaderCompilationCacheFileHeader {
|
||||
char magic[8] = { 'X', 'C', 'B', 'C', '0', '1', '\0', '\0' };
|
||||
Core::uint32 schemaVersion = kShaderCompilationCacheSchemaVersion;
|
||||
Core::uint32 backend = 0;
|
||||
Core::uint32 format = 0;
|
||||
Core::uint32 reserved = 0;
|
||||
Core::uint64 payloadSize = 0;
|
||||
Core::uint64 compileKeyHigh = 0;
|
||||
Core::uint64 compileKeyLow = 0;
|
||||
};
|
||||
|
||||
Containers::String MakeError(const char* message) {
|
||||
return Containers::String(message == nullptr ? "" : message);
|
||||
}
|
||||
|
||||
std::string ToStdString(const Containers::String& text) {
|
||||
return std::string(text.CStr(), text.Length());
|
||||
}
|
||||
|
||||
Containers::String ToContainersString(const std::string& text) {
|
||||
return Containers::String(text.c_str(), text.size());
|
||||
}
|
||||
|
||||
std::vector<std::string> SplitFields(const std::string& line) {
|
||||
std::vector<std::string> fields;
|
||||
std::stringstream stream(line);
|
||||
std::string field;
|
||||
while (std::getline(stream, field, '\t')) {
|
||||
fields.push_back(field);
|
||||
}
|
||||
return fields;
|
||||
}
|
||||
|
||||
bool IsExpectedMagic(const ShaderCompilationCacheFileHeader& header) {
|
||||
return std::memcmp(header.magic, "XCBC01", 6) == 0 &&
|
||||
header.schemaVersion == kShaderCompilationCacheSchemaVersion;
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void ShaderCompileKey::Normalize() {
|
||||
shaderPath = shaderPath.Trim();
|
||||
sourceHash = sourceHash.Trim();
|
||||
dependencyHash = dependencyHash.Trim();
|
||||
passName = passName.Trim();
|
||||
entryPoint = entryPoint.Trim();
|
||||
profile = profile.Trim();
|
||||
compilerName = compilerName.Trim();
|
||||
compilerVersion = compilerVersion.Trim();
|
||||
optionsSignature = optionsSignature.Trim();
|
||||
|
||||
std::vector<std::string> normalizedKeywords;
|
||||
normalizedKeywords.reserve(keywords.Size());
|
||||
for (const Containers::String& keyword : keywords) {
|
||||
const Containers::String trimmedKeyword = keyword.Trim();
|
||||
if (!trimmedKeyword.Empty()) {
|
||||
normalizedKeywords.push_back(ToStdString(trimmedKeyword));
|
||||
}
|
||||
}
|
||||
|
||||
std::sort(normalizedKeywords.begin(), normalizedKeywords.end());
|
||||
normalizedKeywords.erase(
|
||||
std::unique(normalizedKeywords.begin(), normalizedKeywords.end()),
|
||||
normalizedKeywords.end());
|
||||
|
||||
Containers::Array<Containers::String> canonicalKeywords;
|
||||
canonicalKeywords.Reserve(normalizedKeywords.size());
|
||||
for (const std::string& keyword : normalizedKeywords) {
|
||||
canonicalKeywords.PushBack(ToContainersString(keyword));
|
||||
}
|
||||
keywords = std::move(canonicalKeywords);
|
||||
}
|
||||
|
||||
Containers::String ShaderCompileKey::BuildSignature() const {
|
||||
ShaderCompileKey normalizedKey = *this;
|
||||
normalizedKey.Normalize();
|
||||
|
||||
Containers::String signature;
|
||||
signature += "shader=";
|
||||
signature += normalizedKey.shaderPath;
|
||||
signature += "\nsourceHash=";
|
||||
signature += normalizedKey.sourceHash;
|
||||
signature += "\ndependencyHash=";
|
||||
signature += normalizedKey.dependencyHash;
|
||||
signature += "\npass=";
|
||||
signature += normalizedKey.passName;
|
||||
signature += "\nentry=";
|
||||
signature += normalizedKey.entryPoint;
|
||||
signature += "\nprofile=";
|
||||
signature += normalizedKey.profile;
|
||||
signature += "\ncompiler=";
|
||||
signature += normalizedKey.compilerName;
|
||||
signature += "\ncompilerVersion=";
|
||||
signature += normalizedKey.compilerVersion;
|
||||
signature += "\noptions=";
|
||||
signature += normalizedKey.optionsSignature;
|
||||
signature += "\nstage=";
|
||||
signature += ToContainersString(std::to_string(static_cast<Core::uint32>(normalizedKey.stage)));
|
||||
signature += "\nsourceLanguage=";
|
||||
signature += ToContainersString(std::to_string(static_cast<Core::uint32>(normalizedKey.sourceLanguage)));
|
||||
signature += "\nbackend=";
|
||||
signature += ToContainersString(std::to_string(static_cast<Core::uint32>(normalizedKey.backend)));
|
||||
|
||||
for (const Containers::String& keyword : normalizedKey.keywords) {
|
||||
signature += "\nkeyword=";
|
||||
signature += keyword;
|
||||
}
|
||||
|
||||
return signature;
|
||||
}
|
||||
|
||||
Containers::String ShaderCompileKey::BuildCacheKey() const {
|
||||
return HashStringToAssetGUID(BuildSignature()).ToString();
|
||||
}
|
||||
|
||||
void ShaderCompilationCache::Initialize(const Containers::String& libraryRoot) {
|
||||
m_libraryRoot = libraryRoot.Trim();
|
||||
m_databasePath = m_libraryRoot + "/shadercache.db";
|
||||
LoadDatabase();
|
||||
}
|
||||
|
||||
void ShaderCompilationCache::Shutdown() {
|
||||
m_libraryRoot.Clear();
|
||||
m_databasePath.Clear();
|
||||
m_records.clear();
|
||||
}
|
||||
|
||||
Containers::String ShaderCompilationCache::BuildCacheKey(const ShaderCompileKey& key) const {
|
||||
return key.BuildCacheKey();
|
||||
}
|
||||
|
||||
Containers::String ShaderCompilationCache::BuildBackendDirectoryName(ShaderBackend backend) {
|
||||
switch (backend) {
|
||||
case ShaderBackend::D3D12:
|
||||
return Containers::String("D3D12");
|
||||
case ShaderBackend::OpenGL:
|
||||
return Containers::String("OpenGL");
|
||||
case ShaderBackend::Vulkan:
|
||||
return Containers::String("Vulkan");
|
||||
case ShaderBackend::Generic:
|
||||
default:
|
||||
return Containers::String("Generic");
|
||||
}
|
||||
}
|
||||
|
||||
AssetGUID ShaderCompilationCache::ComputeKeyGuid(const Containers::String& cacheKey) {
|
||||
AssetGUID guid;
|
||||
if (AssetGUID::TryParse(cacheKey, guid)) {
|
||||
return guid;
|
||||
}
|
||||
return HashStringToAssetGUID(cacheKey);
|
||||
}
|
||||
|
||||
Containers::String ShaderCompilationCache::BuildCacheRelativePath(const ShaderCompileKey& key) const {
|
||||
const Containers::String cacheKey = BuildCacheKey(key);
|
||||
const Containers::String shard =
|
||||
cacheKey.Length() >= 2 ? cacheKey.Substring(0, 2) : Containers::String("00");
|
||||
return Containers::String("ShaderCache/") +
|
||||
BuildBackendDirectoryName(key.backend) +
|
||||
"/" +
|
||||
shard +
|
||||
"/" +
|
||||
cacheKey +
|
||||
".xcbc";
|
||||
}
|
||||
|
||||
Containers::String ShaderCompilationCache::BuildCacheAbsolutePath(const ShaderCompileKey& key) const {
|
||||
if (m_libraryRoot.Empty()) {
|
||||
return Containers::String();
|
||||
}
|
||||
|
||||
return m_libraryRoot + "/" + BuildCacheRelativePath(key);
|
||||
}
|
||||
|
||||
void ShaderCompilationCache::LoadDatabase() {
|
||||
m_records.clear();
|
||||
|
||||
std::ifstream input(m_databasePath.CStr());
|
||||
if (!input.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
std::string line;
|
||||
while (std::getline(input, line)) {
|
||||
if (line.empty() || line[0] == '#') {
|
||||
continue;
|
||||
}
|
||||
|
||||
const std::vector<std::string> fields = SplitFields(line);
|
||||
if (fields.size() < 5) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ShaderCacheRecord record;
|
||||
record.backend = static_cast<ShaderBackend>(std::stoul(fields[1]));
|
||||
record.relativePath = ToContainersString(fields[2]);
|
||||
record.format = static_cast<ShaderBytecodeFormat>(std::stoul(fields[3]));
|
||||
record.payloadSize = static_cast<Core::uint64>(std::stoull(fields[4]));
|
||||
m_records[fields[0]] = record;
|
||||
}
|
||||
}
|
||||
|
||||
void ShaderCompilationCache::SaveDatabase() const {
|
||||
if (m_databasePath.Empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const fs::path dbPath(m_databasePath.CStr());
|
||||
std::error_code ec;
|
||||
if (!dbPath.parent_path().empty()) {
|
||||
fs::create_directories(dbPath.parent_path(), ec);
|
||||
if (ec) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
std::ofstream output(dbPath, std::ios::out | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
output << "# compileKey\tbackend\trelativePath\tformat\tpayloadSize\n";
|
||||
for (const auto& [compileKey, record] : m_records) {
|
||||
output << compileKey << '\t'
|
||||
<< static_cast<Core::uint32>(record.backend) << '\t'
|
||||
<< ToStdString(record.relativePath) << '\t'
|
||||
<< static_cast<Core::uint32>(record.format) << '\t'
|
||||
<< record.payloadSize << '\n';
|
||||
}
|
||||
}
|
||||
|
||||
bool ShaderCompilationCache::Store(const ShaderCacheEntry& entry,
|
||||
Containers::String* outErrorMessage) {
|
||||
if (!IsInitialized()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache is not initialized.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Containers::String cacheKey = BuildCacheKey(entry.key);
|
||||
const Containers::String relativePath = BuildCacheRelativePath(entry.key);
|
||||
const Containers::String absolutePath = BuildCacheAbsolutePath(entry.key);
|
||||
if (absolutePath.Empty()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache absolute path is empty.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::error_code ec;
|
||||
const fs::path cachePath(absolutePath.CStr());
|
||||
fs::create_directories(cachePath.parent_path(), ec);
|
||||
if (ec) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("Failed to create ShaderCompilationCache directory.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream output(cachePath, std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("Failed to open ShaderCompilationCache output file.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const AssetGUID keyGuid = ComputeKeyGuid(cacheKey);
|
||||
ShaderCompilationCacheFileHeader fileHeader = {};
|
||||
fileHeader.backend = static_cast<Core::uint32>(entry.key.backend);
|
||||
fileHeader.format = static_cast<Core::uint32>(entry.format);
|
||||
fileHeader.payloadSize = static_cast<Core::uint64>(entry.payload.Size());
|
||||
fileHeader.compileKeyHigh = keyGuid.high;
|
||||
fileHeader.compileKeyLow = keyGuid.low;
|
||||
output.write(reinterpret_cast<const char*>(&fileHeader), sizeof(fileHeader));
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("Failed to write ShaderCompilationCache header.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!entry.payload.Empty()) {
|
||||
output.write(reinterpret_cast<const char*>(entry.payload.Data()),
|
||||
static_cast<std::streamsize>(entry.payload.Size()));
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("Failed to write ShaderCompilationCache payload.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
ShaderCacheRecord record;
|
||||
record.backend = entry.key.backend;
|
||||
record.format = entry.format;
|
||||
record.relativePath = relativePath;
|
||||
record.payloadSize = static_cast<Core::uint64>(entry.payload.Size());
|
||||
m_records[ToStdString(cacheKey)] = record;
|
||||
SaveDatabase();
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ShaderCompilationCache::TryLoad(const ShaderCompileKey& key,
|
||||
ShaderCacheEntry& outEntry,
|
||||
Containers::String* outErrorMessage) const {
|
||||
outEntry = {};
|
||||
if (!IsInitialized()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache is not initialized.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const Containers::String cacheKey = BuildCacheKey(key);
|
||||
Containers::String relativePath = BuildCacheRelativePath(key);
|
||||
const auto recordIt = m_records.find(ToStdString(cacheKey));
|
||||
if (recordIt != m_records.end() && !recordIt->second.relativePath.Empty()) {
|
||||
relativePath = recordIt->second.relativePath;
|
||||
}
|
||||
|
||||
const Containers::String absolutePath = m_libraryRoot + "/" + relativePath;
|
||||
std::ifstream input(absolutePath.CStr(), std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache entry does not exist.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
ShaderCompilationCacheFileHeader fileHeader = {};
|
||||
input.read(reinterpret_cast<char*>(&fileHeader), sizeof(fileHeader));
|
||||
if (!input || !IsExpectedMagic(fileHeader)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache header is invalid.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
const AssetGUID expectedKeyGuid = ComputeKeyGuid(cacheKey);
|
||||
const AssetGUID actualKeyGuid(fileHeader.compileKeyHigh, fileHeader.compileKeyLow);
|
||||
if (expectedKeyGuid != actualKeyGuid ||
|
||||
fileHeader.backend != static_cast<Core::uint32>(key.backend)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("ShaderCompilationCache entry does not match the requested key.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
payload.ResizeUninitialized(static_cast<size_t>(fileHeader.payloadSize));
|
||||
if (fileHeader.payloadSize > 0) {
|
||||
input.read(reinterpret_cast<char*>(payload.Data()),
|
||||
static_cast<std::streamsize>(fileHeader.payloadSize));
|
||||
if (!input) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = MakeError("Failed to read ShaderCompilationCache payload.");
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outEntry.key = key;
|
||||
outEntry.format = static_cast<ShaderBytecodeFormat>(fileHeader.format);
|
||||
outEntry.payload = std::move(payload);
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace Resources
|
||||
} // namespace XCEngine
|
||||
@@ -1,9 +1,11 @@
|
||||
#include <XCEngine/Resources/Texture/TextureLoader.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Resources/BuiltinResources.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Resources/Texture/TextureImportSettings.h>
|
||||
#include <stb_image.h>
|
||||
#include <cstring>
|
||||
#include <filesystem>
|
||||
#include <fstream>
|
||||
|
||||
@@ -12,6 +14,32 @@ namespace Resources {
|
||||
|
||||
namespace {
|
||||
|
||||
Containers::String GetPathExtensionString(const Containers::String& path) {
|
||||
const std::filesystem::path fsPath(path.CStr());
|
||||
const std::string extension = fsPath.has_extension()
|
||||
? fsPath.extension().string()
|
||||
: std::string();
|
||||
if (!extension.empty() && extension[0] == '.') {
|
||||
return Containers::String(extension.substr(1).c_str());
|
||||
}
|
||||
|
||||
return Containers::String(extension.c_str());
|
||||
}
|
||||
|
||||
Containers::String GetTexturePathExtension(const Containers::String& path) {
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
const Containers::String entryExtension = GetPathExtensionString(entryName);
|
||||
if (!entryExtension.Empty()) {
|
||||
return entryExtension;
|
||||
}
|
||||
return GetPathExtensionString(containerPath);
|
||||
}
|
||||
|
||||
return GetPathExtensionString(path);
|
||||
}
|
||||
|
||||
Containers::String GetResourceNameFromPath(const Containers::String& path) {
|
||||
const std::filesystem::path filePath(path.CStr());
|
||||
const std::string fileName = filePath.filename().string();
|
||||
@@ -76,37 +104,76 @@ TextureFormat ResolveDecodedTextureFormat(const ImportSettings* settings, bool i
|
||||
}
|
||||
|
||||
LoadResult LoadTextureArtifact(const Containers::String& path) {
|
||||
auto readFileData = [](const std::filesystem::path& filePath,
|
||||
Containers::Array<Core::uint8>& outData,
|
||||
bool& opened) {
|
||||
opened = false;
|
||||
outData.Clear();
|
||||
|
||||
Containers::String payloadError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(filePath.generic_string().c_str()),
|
||||
ResourceType::Texture,
|
||||
outData,
|
||||
&payloadError)) {
|
||||
opened = true;
|
||||
return;
|
||||
}
|
||||
|
||||
std::ifstream input(filePath, std::ios::binary | std::ios::ate);
|
||||
if (!input.is_open()) {
|
||||
return;
|
||||
}
|
||||
|
||||
opened = true;
|
||||
const std::streamsize size = input.tellg();
|
||||
if (size <= 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
input.seekg(0, std::ios::beg);
|
||||
outData.Resize(static_cast<size_t>(size));
|
||||
if (!input.read(reinterpret_cast<char*>(outData.Data()), size)) {
|
||||
outData.Clear();
|
||||
}
|
||||
};
|
||||
|
||||
Containers::Array<Core::uint8> data;
|
||||
bool opened = false;
|
||||
std::filesystem::path resolvedPath(path.CStr());
|
||||
if (!resolvedPath.is_absolute() && !std::filesystem::exists(resolvedPath)) {
|
||||
readFileData(resolvedPath, data, opened);
|
||||
if (!opened && !resolvedPath.is_absolute()) {
|
||||
const Containers::String& resourceRoot = ResourceManager::Get().GetResourceRoot();
|
||||
if (!resourceRoot.Empty()) {
|
||||
resolvedPath = std::filesystem::path(resourceRoot.CStr()) / resolvedPath;
|
||||
readFileData(resolvedPath, data, opened);
|
||||
}
|
||||
}
|
||||
|
||||
std::ifstream input(resolvedPath, std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
if (!opened || data.Size() < sizeof(TextureArtifactHeader)) {
|
||||
return LoadResult(Containers::String("Failed to read texture artifact: ") + path);
|
||||
}
|
||||
|
||||
TextureArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to parse texture artifact header: ") + path);
|
||||
}
|
||||
std::memcpy(&header, data.Data(), sizeof(header));
|
||||
|
||||
const std::string magic(header.magic, header.magic + 7);
|
||||
if (magic != "XCTEX01") {
|
||||
return LoadResult(Containers::String("Invalid texture artifact magic: ") + path);
|
||||
}
|
||||
|
||||
const size_t payloadOffset = sizeof(TextureArtifactHeader);
|
||||
if (header.pixelDataSize > static_cast<Core::uint64>(data.Size() - payloadOffset)) {
|
||||
return LoadResult(Containers::String("Failed to read texture artifact payload: ") + path);
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> pixelData;
|
||||
pixelData.Resize(static_cast<size_t>(header.pixelDataSize));
|
||||
if (header.pixelDataSize > 0) {
|
||||
input.read(reinterpret_cast<char*>(pixelData.Data()), static_cast<std::streamsize>(header.pixelDataSize));
|
||||
if (!input) {
|
||||
return LoadResult(Containers::String("Failed to read texture artifact payload: ") + path);
|
||||
}
|
||||
std::memcpy(
|
||||
pixelData.Data(),
|
||||
data.Data() + payloadOffset,
|
||||
static_cast<size_t>(header.pixelDataSize));
|
||||
}
|
||||
|
||||
return CreateTextureResource(path,
|
||||
@@ -150,7 +217,7 @@ bool TextureLoader::CanLoad(const Containers::String& path) const {
|
||||
return true;
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
Containers::String ext = GetTexturePathExtension(path).ToLower();
|
||||
|
||||
return ext == "png" || ext == "jpg" || ext == "jpeg" ||
|
||||
ext == "tga" || ext == "bmp" || ext == "gif" ||
|
||||
@@ -162,7 +229,7 @@ LoadResult TextureLoader::Load(const Containers::String& path, const ImportSetti
|
||||
return CreateBuiltinTextureResource(path);
|
||||
}
|
||||
|
||||
Containers::String ext = GetExtension(path).ToLower();
|
||||
Containers::String ext = GetTexturePathExtension(path).ToLower();
|
||||
|
||||
if (!CanLoad(path)) {
|
||||
return LoadResult(Containers::String("Unsupported texture format: ") + ext);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <XCEngine/Resources/UI/UIDocumentCompiler.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
|
||||
@@ -53,7 +54,15 @@ Containers::String FormatDiagnosticMessage(const Containers::String& path,
|
||||
message;
|
||||
}
|
||||
|
||||
void WriteString(std::ofstream& stream, const Containers::String& value) {
|
||||
bool BuildSchemaDefinition(
|
||||
const Containers::String& sourcePath,
|
||||
const UIDocumentNode& rootNode,
|
||||
const Containers::String& displayName,
|
||||
UISchemaDefinition& outDefinition,
|
||||
Containers::Array<UIDocumentDiagnostic>& diagnostics,
|
||||
Containers::String& outErrorMessage);
|
||||
|
||||
void WriteString(std::ostream& stream, const Containers::String& value) {
|
||||
const Core::uint32 length = static_cast<Core::uint32>(value.Length());
|
||||
stream.write(reinterpret_cast<const char*>(&length), sizeof(length));
|
||||
if (length > 0) {
|
||||
@@ -61,7 +70,7 @@ void WriteString(std::ofstream& stream, const Containers::String& value) {
|
||||
}
|
||||
}
|
||||
|
||||
bool ReadString(std::ifstream& stream, Containers::String& outValue) {
|
||||
bool ReadString(std::istream& stream, Containers::String& outValue) {
|
||||
Core::uint32 length = 0;
|
||||
stream.read(reinterpret_cast<char*>(&length), sizeof(length));
|
||||
if (!stream) {
|
||||
@@ -83,7 +92,7 @@ bool ReadString(std::ifstream& stream, Containers::String& outValue) {
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteNode(std::ofstream& stream, const UIDocumentNode& node) {
|
||||
bool WriteNode(std::ostream& stream, const UIDocumentNode& node) {
|
||||
WriteString(stream, node.tagName);
|
||||
|
||||
UIDocumentArtifactNodeHeader header;
|
||||
@@ -114,7 +123,7 @@ bool WriteNode(std::ofstream& stream, const UIDocumentNode& node) {
|
||||
return static_cast<bool>(stream);
|
||||
}
|
||||
|
||||
bool ReadNode(std::ifstream& stream, UIDocumentNode& outNode) {
|
||||
bool ReadNode(std::istream& stream, UIDocumentNode& outNode) {
|
||||
if (!ReadString(stream, outNode.tagName)) {
|
||||
return false;
|
||||
}
|
||||
@@ -178,7 +187,7 @@ struct UIDocumentArtifactSchemaAttributeHeader {
|
||||
Core::uint32 restrictDocumentKind = 0;
|
||||
};
|
||||
|
||||
bool WriteSchemaAttribute(std::ofstream& stream, const UISchemaAttributeDefinition& attribute) {
|
||||
bool WriteSchemaAttribute(std::ostream& stream, const UISchemaAttributeDefinition& attribute) {
|
||||
WriteString(stream, attribute.name);
|
||||
|
||||
UIDocumentArtifactSchemaAttributeHeader header;
|
||||
@@ -204,7 +213,7 @@ bool WriteSchemaAttribute(std::ofstream& stream, const UISchemaAttributeDefiniti
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadSchemaAttribute(std::ifstream& stream, UISchemaAttributeDefinition& outAttribute) {
|
||||
bool ReadSchemaAttribute(std::istream& stream, UISchemaAttributeDefinition& outAttribute) {
|
||||
if (!ReadString(stream, outAttribute.name)) {
|
||||
return false;
|
||||
}
|
||||
@@ -234,7 +243,7 @@ bool ReadSchemaAttribute(std::ifstream& stream, UISchemaAttributeDefinition& out
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteSchemaElement(std::ofstream& stream, const UISchemaElementDefinition& element) {
|
||||
bool WriteSchemaElement(std::ostream& stream, const UISchemaElementDefinition& element) {
|
||||
WriteString(stream, element.tagName);
|
||||
|
||||
UIDocumentArtifactSchemaElementHeader header;
|
||||
@@ -264,7 +273,7 @@ bool WriteSchemaElement(std::ofstream& stream, const UISchemaElementDefinition&
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadSchemaElement(std::ifstream& stream, UISchemaElementDefinition& outElement) {
|
||||
bool ReadSchemaElement(std::istream& stream, UISchemaElementDefinition& outElement) {
|
||||
if (!ReadString(stream, outElement.tagName)) {
|
||||
return false;
|
||||
}
|
||||
@@ -302,7 +311,7 @@ bool ReadSchemaElement(std::ifstream& stream, UISchemaElementDefinition& outElem
|
||||
return true;
|
||||
}
|
||||
|
||||
bool WriteSchemaDefinition(std::ofstream& stream, const UISchemaDefinition& schemaDefinition) {
|
||||
bool WriteSchemaDefinition(std::ostream& stream, const UISchemaDefinition& schemaDefinition) {
|
||||
WriteString(stream, schemaDefinition.name);
|
||||
|
||||
UIDocumentArtifactSchemaDefinitionHeader header;
|
||||
@@ -322,7 +331,7 @@ bool WriteSchemaDefinition(std::ofstream& stream, const UISchemaDefinition& sche
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ReadSchemaDefinition(std::ifstream& stream, UISchemaDefinition& outSchemaDefinition) {
|
||||
bool ReadSchemaDefinition(std::istream& stream, UISchemaDefinition& outSchemaDefinition) {
|
||||
if (!ReadString(stream, outSchemaDefinition.name)) {
|
||||
return false;
|
||||
}
|
||||
@@ -347,6 +356,193 @@ bool ReadSchemaDefinition(std::ifstream& stream, UISchemaDefinition& outSchemaDe
|
||||
return true;
|
||||
}
|
||||
|
||||
ResourceType GetUIDocumentResourceType(UIDocumentKind kind) {
|
||||
switch (kind) {
|
||||
case UIDocumentKind::View:
|
||||
return ResourceType::UIView;
|
||||
case UIDocumentKind::Theme:
|
||||
return ResourceType::UITheme;
|
||||
case UIDocumentKind::Schema:
|
||||
return ResourceType::UISchema;
|
||||
default:
|
||||
return ResourceType::Unknown;
|
||||
}
|
||||
}
|
||||
|
||||
bool SerializeUIDocumentArtifactPayload(const UIDocumentCompileResult& compileResult,
|
||||
Containers::Array<Core::uint8>& outPayload,
|
||||
Containers::String* outErrorMessage) {
|
||||
outPayload.Clear();
|
||||
|
||||
if (!compileResult.succeeded || !compileResult.document.valid) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "UI document compile result is invalid.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ostringstream output(std::ios::binary | std::ios::out);
|
||||
|
||||
UIDocumentArtifactFileHeader header;
|
||||
header.kind = static_cast<Core::uint32>(compileResult.document.kind);
|
||||
header.dependencyCount = static_cast<Core::uint32>(compileResult.document.dependencies.Size());
|
||||
header.diagnosticCount = static_cast<Core::uint32>(compileResult.document.diagnostics.Size());
|
||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Failed to serialize UI document artifact header.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteString(output, compileResult.document.sourcePath);
|
||||
WriteString(output, compileResult.document.displayName);
|
||||
if (!WriteNode(output, compileResult.document.rootNode)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Failed to serialize UI document node tree.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!WriteSchemaDefinition(output, compileResult.document.schemaDefinition)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Failed to serialize UI document schema definition.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const Containers::String& dependency : compileResult.document.dependencies) {
|
||||
WriteString(output, dependency);
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Failed to serialize UI document dependencies.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const UIDocumentDiagnostic& diagnostic : compileResult.document.diagnostics) {
|
||||
UIDocumentArtifactDiagnosticHeader diagnosticHeader;
|
||||
diagnosticHeader.severity = static_cast<Core::uint32>(diagnostic.severity);
|
||||
diagnosticHeader.line = diagnostic.location.line;
|
||||
diagnosticHeader.column = diagnostic.location.column;
|
||||
output.write(reinterpret_cast<const char*>(&diagnosticHeader), sizeof(diagnosticHeader));
|
||||
WriteString(output, diagnostic.message);
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "Failed to serialize UI document diagnostics.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
const std::string serialized = output.str();
|
||||
outPayload.Resize(serialized.size());
|
||||
if (!serialized.empty()) {
|
||||
std::memcpy(outPayload.Data(), serialized.data(), serialized.size());
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
bool ParseUIDocumentArtifactStream(std::istream& input,
|
||||
const Containers::String& artifactPath,
|
||||
UIDocumentKind expectedKind,
|
||||
UIDocumentCompileResult& outResult) {
|
||||
UIDocumentArtifactFileHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact header: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
const UIDocumentArtifactFileHeader expectedHeader;
|
||||
if (std::memcmp(header.magic, expectedHeader.magic, sizeof(header.magic)) != 0) {
|
||||
outResult.errorMessage = Containers::String("Invalid UI document artifact magic: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.schemaVersion != 1u &&
|
||||
header.schemaVersion != kUIDocumentArtifactSchemaVersion) {
|
||||
outResult.errorMessage = Containers::String("Unsupported UI document artifact schema version: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.kind != static_cast<Core::uint32>(expectedKind)) {
|
||||
outResult.errorMessage = Containers::String("UI document artifact kind mismatch: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
outResult.document.kind = expectedKind;
|
||||
if (!ReadString(input, outResult.document.sourcePath) ||
|
||||
!ReadString(input, outResult.document.displayName) ||
|
||||
!ReadNode(input, outResult.document.rootNode)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact body: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
outResult.document.schemaDefinition.Clear();
|
||||
if (header.schemaVersion >= 2u) {
|
||||
if (!ReadSchemaDefinition(input, outResult.document.schemaDefinition)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact schema definition: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outResult.document.dependencies.Clear();
|
||||
outResult.document.dependencies.Reserve(header.dependencyCount);
|
||||
for (Core::uint32 index = 0; index < header.dependencyCount; ++index) {
|
||||
Containers::String dependency;
|
||||
if (!ReadString(input, dependency)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact dependencies: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
outResult.document.dependencies.PushBack(std::move(dependency));
|
||||
}
|
||||
|
||||
outResult.document.diagnostics.Clear();
|
||||
outResult.document.diagnostics.Reserve(header.diagnosticCount);
|
||||
for (Core::uint32 index = 0; index < header.diagnosticCount; ++index) {
|
||||
UIDocumentArtifactDiagnosticHeader diagnosticHeader;
|
||||
input.read(reinterpret_cast<char*>(&diagnosticHeader), sizeof(diagnosticHeader));
|
||||
if (!input) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact diagnostic header: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
UIDocumentDiagnostic diagnostic;
|
||||
diagnostic.severity = static_cast<UIDocumentDiagnosticSeverity>(diagnosticHeader.severity);
|
||||
diagnostic.location.line = diagnosticHeader.line;
|
||||
diagnostic.location.column = diagnosticHeader.column;
|
||||
if (!ReadString(input, diagnostic.message)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact diagnostic message: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
outResult.document.diagnostics.PushBack(std::move(diagnostic));
|
||||
}
|
||||
|
||||
if (expectedKind == UIDocumentKind::Schema) {
|
||||
const Containers::String sourcePath =
|
||||
outResult.document.sourcePath.Empty()
|
||||
? artifactPath
|
||||
: outResult.document.sourcePath;
|
||||
if (!outResult.document.schemaDefinition.valid &&
|
||||
!BuildSchemaDefinition(
|
||||
sourcePath,
|
||||
outResult.document.rootNode,
|
||||
outResult.document.displayName,
|
||||
outResult.document.schemaDefinition,
|
||||
outResult.document.diagnostics,
|
||||
outResult.errorMessage)) {
|
||||
outResult.succeeded = false;
|
||||
outResult.document.valid = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outResult.document.valid = true;
|
||||
outResult.succeeded = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
bool IsNameStartChar(char ch) {
|
||||
return std::isalpha(static_cast<unsigned char>(ch)) != 0 ||
|
||||
ch == '_' ||
|
||||
@@ -1548,74 +1744,27 @@ bool CompileUIDocument(const UIDocumentCompileRequest& request,
|
||||
bool WriteUIDocumentArtifact(const Containers::String& artifactPath,
|
||||
const UIDocumentCompileResult& compileResult,
|
||||
Containers::String* outErrorMessage) {
|
||||
if (!compileResult.succeeded || !compileResult.document.valid) {
|
||||
Containers::Array<Core::uint8> payload;
|
||||
if (!SerializeUIDocumentArtifactPayload(compileResult, payload, outErrorMessage)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const ResourceType resourceType = GetUIDocumentResourceType(compileResult.document.kind);
|
||||
if (resourceType == ResourceType::Unknown) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = "UI document compile result is invalid.";
|
||||
*outErrorMessage = "UI document kind is not importable.";
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
std::ofstream output(artifactPath.CStr(), std::ios::binary | std::ios::trunc);
|
||||
if (!output.is_open()) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Unable to write UI document artifact: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
UIDocumentArtifactFileHeader header;
|
||||
header.kind = static_cast<Core::uint32>(compileResult.document.kind);
|
||||
header.dependencyCount = static_cast<Core::uint32>(compileResult.document.dependencies.Size());
|
||||
header.diagnosticCount = static_cast<Core::uint32>(compileResult.document.diagnostics.Size());
|
||||
output.write(reinterpret_cast<const char*>(&header), sizeof(header));
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write UI document artifact header: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
WriteString(output, compileResult.document.sourcePath);
|
||||
WriteString(output, compileResult.document.displayName);
|
||||
if (!WriteNode(output, compileResult.document.rootNode)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write UI document node tree: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
if (!WriteSchemaDefinition(output, compileResult.document.schemaDefinition)) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write UI document schema definition: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
for (const Containers::String& dependency : compileResult.document.dependencies) {
|
||||
WriteString(output, dependency);
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write UI document dependencies: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (const UIDocumentDiagnostic& diagnostic : compileResult.document.diagnostics) {
|
||||
UIDocumentArtifactDiagnosticHeader diagnosticHeader;
|
||||
diagnosticHeader.severity = static_cast<Core::uint32>(diagnostic.severity);
|
||||
diagnosticHeader.line = diagnostic.location.line;
|
||||
diagnosticHeader.column = diagnostic.location.column;
|
||||
output.write(reinterpret_cast<const char*>(&diagnosticHeader), sizeof(diagnosticHeader));
|
||||
WriteString(output, diagnostic.message);
|
||||
if (!output) {
|
||||
if (outErrorMessage != nullptr) {
|
||||
*outErrorMessage = Containers::String("Failed to write UI document diagnostics: ") + artifactPath;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
ArtifactContainerWriter writer;
|
||||
ArtifactContainerEntry mainEntry;
|
||||
mainEntry.name = "main";
|
||||
mainEntry.resourceType = resourceType;
|
||||
mainEntry.localID = kMainAssetLocalID;
|
||||
mainEntry.payload = std::move(payload);
|
||||
writer.AddEntry(std::move(mainEntry));
|
||||
return writer.WriteToFile(artifactPath, outErrorMessage);
|
||||
}
|
||||
|
||||
bool LoadUIDocumentArtifact(const Containers::String& artifactPath,
|
||||
@@ -1623,106 +1772,30 @@ bool LoadUIDocumentArtifact(const Containers::String& artifactPath,
|
||||
UIDocumentCompileResult& outResult) {
|
||||
outResult = UIDocumentCompileResult();
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
Containers::String containerError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
artifactPath,
|
||||
GetUIDocumentResourceType(expectedKind),
|
||||
payload,
|
||||
&containerError)) {
|
||||
std::string inputBytes;
|
||||
inputBytes.resize(payload.Size());
|
||||
if (!payload.Empty()) {
|
||||
std::memcpy(inputBytes.data(), payload.Data(), payload.Size());
|
||||
}
|
||||
|
||||
std::istringstream input(inputBytes, std::ios::binary | std::ios::in);
|
||||
return ParseUIDocumentArtifactStream(input, artifactPath, expectedKind, outResult);
|
||||
}
|
||||
|
||||
std::ifstream input(artifactPath.CStr(), std::ios::binary);
|
||||
if (!input.is_open()) {
|
||||
outResult.errorMessage = Containers::String("Unable to open UI document artifact: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
UIDocumentArtifactFileHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
if (!input) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact header: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
const UIDocumentArtifactFileHeader expectedHeader;
|
||||
if (std::memcmp(header.magic, expectedHeader.magic, sizeof(header.magic)) != 0) {
|
||||
outResult.errorMessage = Containers::String("Invalid UI document artifact magic: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.schemaVersion != 1u &&
|
||||
header.schemaVersion != kUIDocumentArtifactSchemaVersion) {
|
||||
outResult.errorMessage = Containers::String("Unsupported UI document artifact schema version: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
if (header.kind != static_cast<Core::uint32>(expectedKind)) {
|
||||
outResult.errorMessage = Containers::String("UI document artifact kind mismatch: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
outResult.document.kind = expectedKind;
|
||||
if (!ReadString(input, outResult.document.sourcePath) ||
|
||||
!ReadString(input, outResult.document.displayName) ||
|
||||
!ReadNode(input, outResult.document.rootNode)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact body: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
outResult.document.schemaDefinition.Clear();
|
||||
if (header.schemaVersion >= 2u) {
|
||||
if (!ReadSchemaDefinition(input, outResult.document.schemaDefinition)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact schema definition: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outResult.document.dependencies.Clear();
|
||||
outResult.document.dependencies.Reserve(header.dependencyCount);
|
||||
for (Core::uint32 index = 0; index < header.dependencyCount; ++index) {
|
||||
Containers::String dependency;
|
||||
if (!ReadString(input, dependency)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact dependencies: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
outResult.document.dependencies.PushBack(std::move(dependency));
|
||||
}
|
||||
|
||||
outResult.document.diagnostics.Clear();
|
||||
outResult.document.diagnostics.Reserve(header.diagnosticCount);
|
||||
for (Core::uint32 index = 0; index < header.diagnosticCount; ++index) {
|
||||
UIDocumentArtifactDiagnosticHeader diagnosticHeader;
|
||||
input.read(reinterpret_cast<char*>(&diagnosticHeader), sizeof(diagnosticHeader));
|
||||
if (!input) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact diagnostic header: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
UIDocumentDiagnostic diagnostic;
|
||||
diagnostic.severity = static_cast<UIDocumentDiagnosticSeverity>(diagnosticHeader.severity);
|
||||
diagnostic.location.line = diagnosticHeader.line;
|
||||
diagnostic.location.column = diagnosticHeader.column;
|
||||
if (!ReadString(input, diagnostic.message)) {
|
||||
outResult.errorMessage = Containers::String("Failed to read UI document artifact diagnostic message: ") + artifactPath;
|
||||
return false;
|
||||
}
|
||||
|
||||
outResult.document.diagnostics.PushBack(std::move(diagnostic));
|
||||
}
|
||||
|
||||
if (expectedKind == UIDocumentKind::Schema) {
|
||||
const Containers::String sourcePath =
|
||||
outResult.document.sourcePath.Empty()
|
||||
? artifactPath
|
||||
: outResult.document.sourcePath;
|
||||
if (!outResult.document.schemaDefinition.valid &&
|
||||
!BuildSchemaDefinition(
|
||||
sourcePath,
|
||||
outResult.document.rootNode,
|
||||
outResult.document.displayName,
|
||||
outResult.document.schemaDefinition,
|
||||
outResult.document.diagnostics,
|
||||
outResult.errorMessage)) {
|
||||
outResult.succeeded = false;
|
||||
outResult.document.valid = false;
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
outResult.document.valid = true;
|
||||
outResult.succeeded = true;
|
||||
return true;
|
||||
return ParseUIDocumentArtifactStream(input, artifactPath, expectedKind, outResult);
|
||||
}
|
||||
|
||||
Containers::String GetUIDocumentDefaultRootTag(UIDocumentKind kind) {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
#include <XCEngine/Resources/UI/UIDocumentLoaders.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
|
||||
#include <filesystem>
|
||||
|
||||
@@ -8,7 +9,14 @@ namespace Resources {
|
||||
namespace {
|
||||
|
||||
Containers::String GetPathExtension(const Containers::String& path) {
|
||||
const std::filesystem::path fsPath(path.CStr());
|
||||
Containers::String normalizedPath = path;
|
||||
Containers::String containerPath;
|
||||
Containers::String entryName;
|
||||
if (TryParseArtifactContainerEntryPath(path, containerPath, entryName)) {
|
||||
normalizedPath = containerPath;
|
||||
}
|
||||
|
||||
const std::filesystem::path fsPath(normalizedPath.CStr());
|
||||
const std::string extension = fsPath.has_extension()
|
||||
? fsPath.extension().generic_string()
|
||||
: std::string();
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
#include <XCEngine/Resources/Volume/VolumeFieldLoader.h>
|
||||
|
||||
#include <XCEngine/Core/Asset/ArtifactContainer.h>
|
||||
#include <XCEngine/Core/Asset/ArtifactFormats.h>
|
||||
#include <XCEngine/Core/Asset/ResourceManager.h>
|
||||
#include <XCEngine/Debug/Logger.h>
|
||||
@@ -82,25 +83,66 @@ std::filesystem::path ResolveVolumeFieldPath(const Containers::String& path) {
|
||||
return resolvedPath.lexically_normal();
|
||||
}
|
||||
|
||||
LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
const std::filesystem::path resolvedPath = ResolveVolumeFieldPath(path);
|
||||
const auto totalStart = std::chrono::steady_clock::now();
|
||||
bool ReadVolumeArtifactData(const Containers::String& path,
|
||||
Containers::Array<Core::uint8>& outData,
|
||||
bool& outReadFromContainer,
|
||||
std::filesystem::path& outResolvedPath) {
|
||||
outData.Clear();
|
||||
outReadFromContainer = false;
|
||||
outResolvedPath = ResolveVolumeFieldPath(path);
|
||||
|
||||
const auto openStart = std::chrono::steady_clock::now();
|
||||
std::ifstream input(resolvedPath, std::ios::binary);
|
||||
const auto openEnd = std::chrono::steady_clock::now();
|
||||
Containers::String containerError;
|
||||
if (ReadArtifactContainerPayloadByPath(
|
||||
Containers::String(outResolvedPath.generic_string().c_str()),
|
||||
ResourceType::VolumeField,
|
||||
outData,
|
||||
&containerError)) {
|
||||
outReadFromContainer = true;
|
||||
return true;
|
||||
}
|
||||
|
||||
std::ifstream input(outResolvedPath, std::ios::binary | std::ios::ate);
|
||||
if (!input.is_open()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const std::streamsize size = input.tellg();
|
||||
if (size < 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
input.seekg(0, std::ios::beg);
|
||||
outData.Resize(static_cast<size_t>(size));
|
||||
if (size == 0) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!input.read(reinterpret_cast<char*>(outData.Data()), size)) {
|
||||
outData.Clear();
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
const auto totalStart = std::chrono::steady_clock::now();
|
||||
Containers::Array<Core::uint8> data;
|
||||
bool readFromContainer = false;
|
||||
std::filesystem::path resolvedPath;
|
||||
const auto readStart = std::chrono::steady_clock::now();
|
||||
if (!ReadVolumeArtifactData(path, data, readFromContainer, resolvedPath)) {
|
||||
return LoadResult(Containers::String("Failed to read volume artifact: ") + path);
|
||||
}
|
||||
const auto readEnd = std::chrono::steady_clock::now();
|
||||
|
||||
const auto headerStart = std::chrono::steady_clock::now();
|
||||
VolumeFieldArtifactHeader header;
|
||||
input.read(reinterpret_cast<char*>(&header), sizeof(header));
|
||||
const auto headerEnd = std::chrono::steady_clock::now();
|
||||
if (!input) {
|
||||
if (data.Size() < sizeof(VolumeFieldArtifactHeader)) {
|
||||
return LoadResult(Containers::String("Failed to parse volume artifact header: ") + path);
|
||||
}
|
||||
|
||||
VolumeFieldArtifactHeader header;
|
||||
std::memcpy(&header, data.Data(), sizeof(header));
|
||||
|
||||
const bool validHeader =
|
||||
std::memcmp(header.magic, "XCVOL02", 7) == 0 &&
|
||||
header.schemaVersion == kVolumeFieldArtifactSchemaVersion &&
|
||||
@@ -110,23 +152,26 @@ LoadResult LoadVolumeFieldArtifact(const Containers::String& path) {
|
||||
}
|
||||
|
||||
Containers::Array<Core::uint8> payload;
|
||||
payload.ResizeUninitialized(static_cast<size_t>(header.payloadSize));
|
||||
const auto payloadReadStart = std::chrono::steady_clock::now();
|
||||
input.read(reinterpret_cast<char*>(payload.Data()), static_cast<std::streamsize>(header.payloadSize));
|
||||
const auto payloadReadEnd = std::chrono::steady_clock::now();
|
||||
if (!input) {
|
||||
const size_t payloadOffset = sizeof(VolumeFieldArtifactHeader);
|
||||
if (header.payloadSize > static_cast<Core::uint64>(data.Size() - payloadOffset)) {
|
||||
return LoadResult(Containers::String("Failed to read volume artifact payload: ") + path);
|
||||
}
|
||||
|
||||
payload.ResizeUninitialized(static_cast<size_t>(header.payloadSize));
|
||||
if (header.payloadSize > 0) {
|
||||
std::memcpy(
|
||||
payload.Data(),
|
||||
data.Data() + payloadOffset,
|
||||
static_cast<size_t>(header.payloadSize));
|
||||
}
|
||||
const auto totalEnd = std::chrono::steady_clock::now();
|
||||
LogVolumeTraceFileSystem(
|
||||
"VolumeFieldLoader Artifact path=" + std::string(path.CStr()) +
|
||||
" resolved=" + resolvedPath.generic_string() +
|
||||
" open_ms=" +
|
||||
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(openEnd - openStart).count()) +
|
||||
" header_ms=" +
|
||||
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(headerEnd - headerStart).count()) +
|
||||
" payload_read_ms=" +
|
||||
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(payloadReadEnd - payloadReadStart).count()) +
|
||||
" mode=" +
|
||||
std::string(readFromContainer ? "container" : "raw") +
|
||||
" read_ms=" +
|
||||
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(readEnd - readStart).count()) +
|
||||
" total_ms=" +
|
||||
std::to_string(std::chrono::duration_cast<std::chrono::milliseconds>(totalEnd - totalStart).count()) +
|
||||
" payload_bytes=" +
|
||||
|
||||
Reference in New Issue
Block a user