Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
88 changes: 80 additions & 8 deletions Axiom/Assets/AssetCooker.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -92,13 +92,56 @@ CookMeshAsset(const std::filesystem::path &ContentRoot,
return std::nullopt;
}

const auto Scene = LoadBasicMeshAsset(SourcePath);
const auto Scene = LoadBasicMeshAssetFromSource(SourcePath);
if (!Scene.has_value()) {
A_CORE_WARN("AssetCooker: failed to import source mesh '{}'",
SourcePath.string());
return std::nullopt;
}

MeshSceneData CookedScene = *Scene;
std::vector<std::string> MaterialAssetPaths(CookedScene.Instances.size());
const std::string AssetStem = RelativeAssetPath.stem().generic_string();
for (size_t InstanceIndex = 0; InstanceIndex < CookedScene.Instances.size();
++InstanceIndex) {
auto &Instance = CookedScene.Instances[InstanceIndex];
if (!Instance.Material) {
continue;
}

std::string TextureAssetPath;
if (Instance.Material->BaseColorTexture &&
Instance.Material->BaseColorTexture->IsValid()) {
const std::filesystem::path RelativeTexturePath =
std::filesystem::path("Generated/MeshTextures") /
(AssetStem + "__" + std::to_string(InstanceIndex));
const auto TextureEntry = CookTextureAsset(
ContentRoot, RelativeTexturePath, *Instance.Material->BaseColorTexture);
if (TextureEntry.has_value()) {
TextureAssetPath = TextureEntry->RelativePath;
}
} else if (!Instance.Material->TextureAssetPath.empty()) {
const auto TextureEntry =
CookTextureAsset(ContentRoot, Instance.Material->TextureAssetPath);
if (TextureEntry.has_value()) {
TextureAssetPath = TextureEntry->RelativePath;
}
}

const std::filesystem::path RelativeMaterialPath =
std::filesystem::path("Generated/MeshMaterials") /
(AssetStem + "__" + std::to_string(InstanceIndex));
const auto MaterialEntry = CookMaterialAsset(
ContentRoot, RelativeMaterialPath,
{.BaseColorFactor = Instance.Material->BaseColorFactor,
.Metallic = Instance.Material->Metallic,
.Roughness = Instance.Material->Roughness,
.TextureAssetPath = TextureAssetPath});
if (MaterialEntry.has_value()) {
MaterialAssetPaths[InstanceIndex] = MaterialEntry->RelativePath;
}
}

const std::filesystem::path CookedRelativePath =
BuildCookedMeshRelativePath(RelativeAssetPath);
const std::filesystem::path CookedAbsolutePath = ContentRoot / CookedRelativePath;
Expand All @@ -110,8 +153,20 @@ CookMeshAsset(const std::filesystem::path &ContentRoot,
return std::nullopt;
}

if (!SaveCookedMeshAsset(CookedAbsolutePath, ToCookedMeshSceneData(*Scene),
Asset)) {
CookedMeshSceneData BinaryScene;
BinaryScene.Instances.reserve(CookedScene.Instances.size());
for (size_t InstanceIndex = 0; InstanceIndex < CookedScene.Instances.size();
++InstanceIndex) {
const auto &Instance = CookedScene.Instances[InstanceIndex];
BinaryScene.Instances.push_back({
.Name = Instance.Name,
.MaterialAssetPath = MaterialAssetPaths[InstanceIndex],
.Mesh = Instance.Mesh,
.Transform = Instance.Transform,
});
}

if (!SaveCookedMeshAsset(CookedAbsolutePath, BinaryScene, Asset)) {
return std::nullopt;
}

Expand Down Expand Up @@ -140,7 +195,6 @@ CookMeshAsset(const std::filesystem::path &ContentRoot,
std::optional<AssetCookManifestEntry>
CookTextureAsset(const std::filesystem::path &ContentRoot,
const std::filesystem::path &RelativeAssetPath) {
const AssetId Asset = AssetIdFromRelativePath(RelativeAssetPath);
const std::filesystem::path SourcePath = ContentRoot / RelativeAssetPath;
const auto SourceHash = HashFileContents(SourcePath);
if (!SourceHash.has_value()) {
Expand All @@ -156,8 +210,22 @@ CookTextureAsset(const std::filesystem::path &ContentRoot,
return std::nullopt;
}

return CookTextureAsset(ContentRoot, RelativeAssetPath, *Texture);
}

std::optional<AssetCookManifestEntry>
CookTextureAsset(const std::filesystem::path &ContentRoot,
const std::filesystem::path &RelativeTexturePath,
const TextureSourceData &Texture) {
const AssetId Asset = AssetIdFromRelativePath(RelativeTexturePath);
if (!Texture.IsValid()) {
A_CORE_WARN("AssetCooker: invalid generated texture '{}'",
RelativeTexturePath.string());
return std::nullopt;
}

const std::filesystem::path CookedRelativePath =
BuildCookedTextureRelativePath(RelativeAssetPath);
BuildCookedTextureRelativePath(RelativeTexturePath);
const std::filesystem::path CookedAbsolutePath = ContentRoot / CookedRelativePath;
std::error_code Ec;
std::filesystem::create_directories(CookedAbsolutePath.parent_path(), Ec);
Expand All @@ -167,7 +235,7 @@ CookTextureAsset(const std::filesystem::path &ContentRoot,
return std::nullopt;
}

if (!SaveCookedTextureAsset(CookedAbsolutePath, *Texture, Asset)) {
if (!SaveCookedTextureAsset(CookedAbsolutePath, Texture, Asset)) {
return std::nullopt;
}

Expand All @@ -176,13 +244,17 @@ CookTextureAsset(const std::filesystem::path &ContentRoot,
AssetCookManifest Manifest =
LoadAssetCookManifest(ManifestPath).value_or(AssetCookManifest{});

const uint64_t SourceHash = HashString(std::to_string(Texture.Width) + "|" +
std::to_string(Texture.Height) + "|" +
std::to_string(Texture.Pixels.size()));

AssetCookManifestEntry Entry{
.Id = Asset,
.Kind = AssetKind::Texture,
.RelativePath = RelativeAssetPath.generic_string(),
.RelativePath = RelativeTexturePath.generic_string(),
.CookedPath = CookedRelativePath.generic_string(),
.FormatVersion = kCookedTextureFormatVersion,
.SourceHash = *SourceHash,
.SourceHash = SourceHash,
};
UpsertManifestEntry(Manifest, Entry);

Expand Down
8 changes: 8 additions & 0 deletions Axiom/Assets/AssetCooker.h
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

#include "Assets/AssetCookManifest.h"
#include "Assets/CookedMaterialAsset.h"
#include "Renderer/Material.h"

#include <filesystem>
#include <optional>
Expand All @@ -20,6 +21,13 @@ std::optional<AssetCookManifestEntry>
CookTextureAsset(const std::filesystem::path &ContentRoot,
const std::filesystem::path &RelativeAssetPath);

// Writes generated cooked texture state under Content/Cooked and updates the
// cook manifest for the provided logical texture path.
std::optional<AssetCookManifestEntry>
CookTextureAsset(const std::filesystem::path &ContentRoot,
const std::filesystem::path &RelativeTexturePath,
const TextureSourceData &Texture);

// Writes generated cooked material state under Content/Cooked and updates the
// cook manifest for the provided logical material path.
std::optional<AssetCookManifestEntry>
Expand Down
2 changes: 1 addition & 1 deletion Axiom/Assets/CookedAssetRuntime.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ LoadCookedMeshAssetIfAvailable(const std::filesystem::path &Path) {
return std::nullopt;
}

return ToRuntimeMeshSceneData(*CookedScene);
return ToRuntimeMeshSceneData(*CookedScene, *ContentRoot);
}

TextureSourceDataRef
Expand Down
74 changes: 70 additions & 4 deletions Axiom/Assets/CookedMeshAsset.cpp
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
#include "CookedMeshAsset.h"

#include "Assets/CookedAssetRuntime.h"
#include "Assets/CookedMaterialAsset.h"
#include "Assets/MeshAsset.h"
#include "Core/Log.h"

#include <array>
Expand All @@ -21,6 +24,16 @@ struct FileHeader {
};

struct InstanceHeader {
uint32_t NameLength;
uint32_t MaterialAssetPathLength;
uint32_t VertexCount;
uint32_t IndexCount;
std::array<float, 3> BoundsMin;
std::array<float, 3> BoundsMax;
std::array<float, 16> Transform;
};

struct InstanceHeaderV1 {
uint32_t NameLength;
uint32_t VertexCount;
uint32_t IndexCount;
Expand All @@ -31,6 +44,7 @@ struct InstanceHeader {

static_assert(std::is_trivially_copyable_v<FileHeader>);
static_assert(std::is_trivially_copyable_v<InstanceHeader>);
static_assert(std::is_trivially_copyable_v<InstanceHeaderV1>);

template <typename T>
bool WriteValue(std::ofstream &Stream, const T &Value) {
Expand Down Expand Up @@ -87,6 +101,8 @@ bool SaveCookedMeshAsset(const std::filesystem::path &Path,
for (const auto &Instance : Scene.Instances) {
const InstanceHeader InstanceMeta{
.NameLength = static_cast<uint32_t>(Instance.Name.size()),
.MaterialAssetPathLength =
static_cast<uint32_t>(Instance.MaterialAssetPath.size()),
.VertexCount = static_cast<uint32_t>(Instance.Mesh.Vertices.size()),
.IndexCount = static_cast<uint32_t>(Instance.Mesh.Indices.size()),
.BoundsMin = {Instance.Mesh.BoundsMin.x, Instance.Mesh.BoundsMin.y,
Expand All @@ -105,6 +121,14 @@ bool SaveCookedMeshAsset(const std::filesystem::path &Path,
return false;
}

if (!Instance.MaterialAssetPath.empty()) {
Stream.write(Instance.MaterialAssetPath.data(),
static_cast<std::streamsize>(
Instance.MaterialAssetPath.size()));
if (!Stream.good())
return false;
}

if (!Instance.Mesh.Vertices.empty()) {
Stream.write(
reinterpret_cast<const char *>(Instance.Mesh.Vertices.data()),
Expand Down Expand Up @@ -145,7 +169,7 @@ LoadCookedMeshAsset(const std::filesystem::path &Path) {
return std::nullopt;
}

if (Header.Version != kCookedMeshFormatVersion) {
if (Header.Version != 1 && Header.Version != kCookedMeshFormatVersion) {
A_CORE_WARN("CookedMeshAsset: unsupported version {} in '{}'",
Header.Version, Path.string());
return std::nullopt;
Expand All @@ -157,7 +181,21 @@ LoadCookedMeshAsset(const std::filesystem::path &Path) {
for (uint32_t InstanceIndex = 0; InstanceIndex < Header.InstanceCount;
++InstanceIndex) {
InstanceHeader InstanceMeta{};
if (!ReadValue(Stream, InstanceMeta)) {
if (Header.Version == 1) {
InstanceHeaderV1 LegacyMeta{};
if (!ReadValue(Stream, LegacyMeta)) {
A_CORE_WARN("CookedMeshAsset: failed to read instance header from '{}'",
Path.string());
return std::nullopt;
}
InstanceMeta.NameLength = LegacyMeta.NameLength;
InstanceMeta.MaterialAssetPathLength = 0;
InstanceMeta.VertexCount = LegacyMeta.VertexCount;
InstanceMeta.IndexCount = LegacyMeta.IndexCount;
InstanceMeta.BoundsMin = LegacyMeta.BoundsMin;
InstanceMeta.BoundsMax = LegacyMeta.BoundsMax;
InstanceMeta.Transform = LegacyMeta.Transform;
} else if (!ReadValue(Stream, InstanceMeta)) {
A_CORE_WARN("CookedMeshAsset: failed to read instance header from '{}'",
Path.string());
return std::nullopt;
Expand All @@ -172,6 +210,16 @@ LoadCookedMeshAsset(const std::filesystem::path &Path) {
return std::nullopt;
}

if (Header.Version >= 2 && InstanceMeta.MaterialAssetPathLength > 0) {
Instance.MaterialAssetPath.resize(InstanceMeta.MaterialAssetPathLength);
Stream.read(Instance.MaterialAssetPath.data(),
static_cast<std::streamsize>(
Instance.MaterialAssetPath.size()));
if (!Stream.good()) {
return std::nullopt;
}
}

Instance.Mesh.Vertices.resize(InstanceMeta.VertexCount);
if (InstanceMeta.VertexCount > 0) {
Stream.read(reinterpret_cast<char *>(Instance.Mesh.Vertices.data()),
Expand Down Expand Up @@ -209,21 +257,39 @@ CookedMeshSceneData ToCookedMeshSceneData(const MeshSceneData &Scene) {
for (const auto &Instance : Scene.Instances) {
Out.Instances.push_back({
.Name = Instance.Name,
.MaterialAssetPath = {},
.Mesh = Instance.Mesh,
.Transform = Instance.Transform,
});
}
return Out;
}

MeshSceneData ToRuntimeMeshSceneData(const CookedMeshSceneData &Scene) {
MeshSceneData ToRuntimeMeshSceneData(const CookedMeshSceneData &Scene,
const std::filesystem::path &ContentRoot) {
MeshSceneData Out;
Out.Instances.reserve(Scene.Instances.size());
for (const auto &Instance : Scene.Instances) {
auto Material = std::make_shared<MaterialInstance>();
if (!Instance.MaterialAssetPath.empty()) {
const auto CookedMaterial =
LoadCookedMaterialAssetIfAvailable(ContentRoot /
Instance.MaterialAssetPath);
if (CookedMaterial.has_value()) {
Material->BaseColorFactor = CookedMaterial->BaseColorFactor;
Material->Metallic = CookedMaterial->Metallic;
Material->Roughness = CookedMaterial->Roughness;
Material->TextureAssetPath = CookedMaterial->TextureAssetPath;
if (!CookedMaterial->TextureAssetPath.empty()) {
Material->BaseColorTexture =
LoadTextureFromFile(ContentRoot / CookedMaterial->TextureAssetPath);
}
}
}
Out.Instances.push_back({
.Name = Instance.Name,
.Mesh = Instance.Mesh,
.Material = std::make_shared<MaterialInstance>(),
.Material = std::move(Material),
.Transform = Instance.Transform,
});
}
Expand Down
6 changes: 4 additions & 2 deletions Axiom/Assets/CookedMeshAsset.h
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@

namespace Axiom::Assets {

constexpr uint32_t kCookedMeshFormatVersion = 1;
constexpr uint32_t kCookedMeshFormatVersion = 2;

struct CookedMeshSceneData {
struct InstanceData {
std::string Name;
std::string MaterialAssetPath;
MeshData Mesh;
glm::mat4 Transform{1.0f};
};
Expand All @@ -30,6 +31,7 @@ std::optional<CookedMeshSceneData>
LoadCookedMeshAsset(const std::filesystem::path &Path);

CookedMeshSceneData ToCookedMeshSceneData(const MeshSceneData &Scene);
MeshSceneData ToRuntimeMeshSceneData(const CookedMeshSceneData &Scene);
MeshSceneData ToRuntimeMeshSceneData(const CookedMeshSceneData &Scene,
const std::filesystem::path &ContentRoot);

} // namespace Axiom::Assets
4 changes: 3 additions & 1 deletion Axiom/Assets/MeshAsset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -524,7 +524,9 @@ std::optional<MeshSceneData> LoadBasicMeshAsset(const std::filesystem::path &Pat
if (!CookedScene.has_value()) {
return std::nullopt;
}
return ToRuntimeMeshSceneData(*CookedScene);
const auto ContentRoot = FindContentRootForPath(Path);
return ToRuntimeMeshSceneData(*CookedScene,
ContentRoot.value_or(Path.parent_path()));
}

if (auto CookedScene = LoadCookedMeshAssetIfAvailable(Path);
Expand Down
Loading
Loading