**Refactor meshes format to support custom vertex layouts and new flexible api to access mesh data**
#3044 #2667
This commit is contained in:
@@ -5,6 +5,9 @@
|
||||
#include "Engine/Content/WeakAssetReference.h"
|
||||
#include "Engine/Serialization/MemoryReadStream.h"
|
||||
#include "Engine/Graphics/Config.h"
|
||||
#include "Engine/Graphics/GPUBuffer.h"
|
||||
#include "Engine/Graphics/Models/MeshBase.h"
|
||||
#include "Engine/Graphics/Shaders/GPUVertexLayout.h"
|
||||
#if GPU_ENABLE_ASYNC_RESOURCES_CREATION
|
||||
#include "Engine/Threading/ThreadPoolTask.h"
|
||||
#define STREAM_TASK_BASE ThreadPoolTask
|
||||
@@ -12,8 +15,11 @@
|
||||
#include "Engine/Threading/MainThreadTask.h"
|
||||
#define STREAM_TASK_BASE MainThreadTask
|
||||
#endif
|
||||
#include "SkinnedModel.h" // TODO: remove this
|
||||
#include "Model.h" // TODO: remove this
|
||||
#if USE_EDITOR
|
||||
#include "Engine/Serialization/MemoryWriteStream.h"
|
||||
#include "Engine/Graphics/Models/ModelData.h"
|
||||
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
|
||||
#endif
|
||||
|
||||
/// <summary>
|
||||
/// Model LOD streaming task.
|
||||
@@ -54,93 +60,26 @@ public:
|
||||
}
|
||||
MemoryReadStream stream(data.Get(), data.Length());
|
||||
|
||||
// Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering)
|
||||
|
||||
// Load model LOD (initialize vertex and index buffers)
|
||||
// TODO: reformat model data storage to be the same for both assets (only custom per-mesh type data like BlendShapes)
|
||||
// Load meshes data and pass data to the GPU buffers
|
||||
Array<MeshBase*> meshes;
|
||||
model->GetMeshes(meshes, _lodIndex);
|
||||
if (model->Is<SkinnedModel>())
|
||||
byte meshVersion = stream.ReadByte();
|
||||
if (meshVersion < 2 || meshVersion > ModelBase::MeshVersion)
|
||||
{
|
||||
byte version = stream.ReadByte();
|
||||
for (int32 i = 0; i < meshes.Count(); i++)
|
||||
{
|
||||
auto& mesh = (SkinnedMesh&)*meshes[i];
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint16 blendShapesCount;
|
||||
stream.ReadUint16(&blendShapesCount);
|
||||
if (blendShapesCount != mesh.BlendShapes.Count())
|
||||
{
|
||||
LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Incorrect blend shapes amount: {} (expected: {})", i, _lodIndex, model->ToString(), blendShapesCount, mesh.BlendShapes.Count());
|
||||
return true;
|
||||
}
|
||||
for (auto& blendShape : mesh.BlendShapes)
|
||||
{
|
||||
blendShape.UseNormals = stream.ReadBool();
|
||||
stream.ReadUint32(&blendShape.MinVertexIndex);
|
||||
stream.ReadUint32(&blendShape.MaxVertexIndex);
|
||||
uint32 blendShapeVertices;
|
||||
stream.ReadUint32(&blendShapeVertices);
|
||||
blendShape.Vertices.Resize(blendShapeVertices);
|
||||
stream.ReadBytes(blendShape.Vertices.Get(), blendShape.Vertices.Count() * sizeof(BlendShapeVertex));
|
||||
}
|
||||
const uint32 indicesCount = triangles * 3;
|
||||
const bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
return true;
|
||||
const auto vb0 = stream.Move<VB0SkinnedElementType>(vertices);
|
||||
const auto ib = stream.Move<byte>(indicesCount * ibStride);
|
||||
|
||||
// Setup GPU resources
|
||||
if (mesh.Load(vertices, triangles, vb0, ib, use16BitIndexBuffer))
|
||||
{
|
||||
LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Vertices: {}, triangles: {}", i, _lodIndex, model->ToString(), vertices, triangles);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
LOG(Warning, "Unsupported mesh version {}", meshVersion);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
|
||||
{
|
||||
for (int32 i = 0; i < meshes.Count(); i++)
|
||||
if (model->LoadMesh(stream, meshVersion, meshes[meshIndex]))
|
||||
{
|
||||
auto& mesh = (Mesh&)*meshes[i];
|
||||
|
||||
// #MODEL_DATA_FORMAT_USAGE
|
||||
uint32 vertices;
|
||||
stream.ReadUint32(&vertices);
|
||||
uint32 triangles;
|
||||
stream.ReadUint32(&triangles);
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
return true;
|
||||
auto vb0 = stream.Move<VB0ElementType>(vertices);
|
||||
auto vb1 = stream.Move<VB1ElementType>(vertices);
|
||||
bool hasColors = stream.ReadBool();
|
||||
VB2ElementType18* vb2 = nullptr;
|
||||
if (hasColors)
|
||||
{
|
||||
vb2 = stream.Move<VB2ElementType18>(vertices);
|
||||
}
|
||||
auto ib = stream.Move<byte>(indicesCount * ibStride);
|
||||
|
||||
// Setup GPU resources
|
||||
if (mesh.Load(vertices, triangles, vb0, vb1, vb2, ib, use16BitIndexBuffer))
|
||||
{
|
||||
LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'. Vertices: {}, triangles: {}", i, _lodIndex, model->ToString(), vertices, triangles);
|
||||
return true;
|
||||
}
|
||||
LOG(Warning, "Cannot initialize mesh {} in LOD{} for model \'{}\'", meshIndex, _lodIndex, model->ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Update residency level
|
||||
// Note: this is running on thread pool task so we must be sure that updated LOD is not used at all (for rendering)
|
||||
model->_loadedLODs++;
|
||||
model->ResidencyChanged();
|
||||
|
||||
@@ -209,6 +148,638 @@ void ModelBase::GetLODData(int32 lodIndex, BytesContainer& data) const
|
||||
GetChunkData(chunkIndex, data);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool ModelBase::Save(bool withMeshDataFromGpu, const StringView& path)
|
||||
{
|
||||
// Validate state
|
||||
if (WaitForLoaded())
|
||||
{
|
||||
LOG(Error, "Asset loading failed. Cannot save it.");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && path.IsEmpty())
|
||||
{
|
||||
LOG(Error, "To save virtual asset asset you need to specify the target asset path location.");
|
||||
return true;
|
||||
}
|
||||
if (withMeshDataFromGpu && IsInMainThread())
|
||||
{
|
||||
LOG(Error, "To save model with GPU mesh buffers it needs to be called from the other thread (not the main thread).");
|
||||
return true;
|
||||
}
|
||||
if (IsVirtual() && !withMeshDataFromGpu)
|
||||
{
|
||||
LOG(Error, "To save virtual model asset you need to specify 'withMeshDataFromGpu' (it has no other storage container to get data).");
|
||||
return true;
|
||||
}
|
||||
|
||||
ScopeLock lock(Locker);
|
||||
|
||||
// Use a temporary chunks for data storage for virtual assets
|
||||
FlaxChunk* tmpChunks[ASSET_FILE_DATA_CHUNKS];
|
||||
Platform::MemoryClear(tmpChunks, sizeof(tmpChunks));
|
||||
Array<FlaxChunk> chunks;
|
||||
if (IsVirtual())
|
||||
chunks.Resize(ASSET_FILE_DATA_CHUNKS);
|
||||
Function<FlaxChunk*(int32)> getChunk = [&](int32 index) -> FlaxChunk* {
|
||||
return IsVirtual() ? tmpChunks[index] = &chunks[index] : GetOrCreateChunk(index);
|
||||
};
|
||||
|
||||
// Save LODs data
|
||||
const int32 lodsCount = GetLODsCount();
|
||||
if (withMeshDataFromGpu)
|
||||
{
|
||||
// Fetch runtime mesh data (from GPU)
|
||||
MemoryWriteStream meshesStream;
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
meshesStream.SetPosition(0);
|
||||
if (SaveLOD(meshesStream, lodIndex))
|
||||
return true;
|
||||
auto lodChunk = getChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex));
|
||||
if (lodChunk == nullptr)
|
||||
return true;
|
||||
lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition());
|
||||
}
|
||||
}
|
||||
else if (!IsVirtual())
|
||||
{
|
||||
// Load all chunks with a mesh data
|
||||
for (int32 lodIndex = 0; lodIndex < lodsCount; lodIndex++)
|
||||
{
|
||||
if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)))
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// Save custom data
|
||||
if (Save(withMeshDataFromGpu, getChunk))
|
||||
return true;
|
||||
|
||||
// Save header data
|
||||
{
|
||||
MemoryWriteStream headerStream(1024);
|
||||
if (SaveHeader(headerStream))
|
||||
return true;
|
||||
auto headerChunk = getChunk(0);
|
||||
ASSERT(headerChunk != nullptr);
|
||||
headerChunk->Data.Copy(headerStream.GetHandle(), headerStream.GetPosition());
|
||||
}
|
||||
|
||||
// Save file
|
||||
AssetInitData data;
|
||||
data.SerializedVersion = GetSerializedVersion();
|
||||
if (IsVirtual())
|
||||
Platform::MemoryCopy(_header.Chunks, tmpChunks, sizeof(_header.Chunks));
|
||||
const bool saveResult = path.HasChars() ? SaveAsset(path, data) : SaveAsset(data, true);
|
||||
if (IsVirtual())
|
||||
Platform::MemoryClear(_header.Chunks, sizeof(_header.Chunks));
|
||||
if (saveResult)
|
||||
{
|
||||
LOG(Error, "Cannot save \'{0}\'", ToString());
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
bool ModelBase::LoadHeader(ReadStream& stream, byte& headerVersion)
|
||||
{
|
||||
// Basic info
|
||||
stream.Read(headerVersion);
|
||||
if (headerVersion < 2 || headerVersion > HeaderVersion)
|
||||
{
|
||||
LOG(Warning, "Unsupported model asset header version {}", headerVersion);
|
||||
return true;
|
||||
}
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
stream.Read(MinScreenSize);
|
||||
|
||||
// Materials
|
||||
int32 materialsCount;
|
||||
stream.Read(materialsCount);
|
||||
if (materialsCount < 0 || materialsCount > 4096)
|
||||
return true;
|
||||
MaterialSlots.Resize(materialsCount, false);
|
||||
Guid materialId;
|
||||
for (auto& slot : MaterialSlots)
|
||||
{
|
||||
stream.Read(materialId);
|
||||
slot.Material = materialId;
|
||||
slot.ShadowsMode = (ShadowsCastingMode)stream.ReadByte();
|
||||
stream.Read(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::LoadMesh(MemoryReadStream& stream, byte meshVersion, MeshBase* mesh, MeshData* dataIfReadOnly)
|
||||
{
|
||||
// Load descriptor
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
uint32 vertices, triangles;
|
||||
stream.Read(vertices);
|
||||
stream.Read(triangles);
|
||||
const uint32 indicesCount = triangles * 3;
|
||||
const bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
if (vertices == 0 || triangles == 0)
|
||||
return true;
|
||||
Array<const void*, FixedAllocation<MODEL_MAX_VB>> vbData;
|
||||
Array<GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>> vbLayout;
|
||||
byte vbCount;
|
||||
stream.Read(vbCount);
|
||||
if (vbCount > MODEL_MAX_VB)
|
||||
return true;
|
||||
vbData.Resize(vbCount);
|
||||
vbLayout.Resize(vbCount);
|
||||
for (int32 i = 0; i < vbCount; i++)
|
||||
{
|
||||
GPUVertexLayout::Elements elements;
|
||||
stream.Read(elements);
|
||||
vbLayout[i] = GPUVertexLayout::Get(elements);
|
||||
}
|
||||
|
||||
// Move over actual mesh data
|
||||
for (int32 i = 0; i < vbCount; i++)
|
||||
{
|
||||
auto layout = vbLayout[i];
|
||||
if (!layout)
|
||||
{
|
||||
LOG(Warning, "Failed to get vertex layout for buffer {}", i);
|
||||
return true;
|
||||
}
|
||||
vbData[i] = stream.Move(vertices * layout->GetStride());
|
||||
}
|
||||
const auto ibData = stream.Move(indicesCount * ibStride);
|
||||
|
||||
// Pass results if read-only
|
||||
if (dataIfReadOnly)
|
||||
{
|
||||
dataIfReadOnly->Vertices = vertices;
|
||||
dataIfReadOnly->Triangles = triangles;
|
||||
dataIfReadOnly->IBStride = ibStride;
|
||||
dataIfReadOnly->VBData = vbData;
|
||||
dataIfReadOnly->VBLayout = vbLayout;
|
||||
dataIfReadOnly->IBData = ibData;
|
||||
return false;
|
||||
}
|
||||
|
||||
// Setup GPU resources
|
||||
return mesh->Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout);
|
||||
}
|
||||
|
||||
#if USE_EDITOR
|
||||
|
||||
bool ModelBase::SaveHeader(WriteStream& stream)
|
||||
{
|
||||
// Basic info
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
stream.Write(HeaderVersion);
|
||||
stream.Write(MinScreenSize);
|
||||
|
||||
// Materials
|
||||
stream.Write(MaterialSlots.Count());
|
||||
for (const auto& slot : MaterialSlots)
|
||||
{
|
||||
stream.Write(slot.Material.GetID());
|
||||
stream.Write((byte)slot.ShadowsMode);
|
||||
stream.Write(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveHeader(WriteStream& stream, const ModelData& modelData)
|
||||
{
|
||||
// Validate data
|
||||
if (modelData.LODs.Count() > MODEL_MAX_LODS)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("LODs"), TEXT("Too many LODs."));
|
||||
return true;
|
||||
}
|
||||
for (int32 lodIndex = 0; lodIndex < modelData.LODs.Count(); lodIndex++)
|
||||
{
|
||||
auto& lod = modelData.LODs[lodIndex];
|
||||
if (lod.Meshes.Count() > MODEL_MAX_MESHES)
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("LOD.Meshes"), TEXT("Too many meshes."));
|
||||
return true;
|
||||
}
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
{
|
||||
auto& mesh = *lod.Meshes[meshIndex];
|
||||
if (mesh.MaterialSlotIndex < 0 || mesh.MaterialSlotIndex >= modelData.Materials.Count())
|
||||
{
|
||||
Log::ArgumentOutOfRangeException(TEXT("MaterialSlotIndex"), TEXT("Incorrect material index used by the mesh."));
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Basic info
|
||||
static_assert(HeaderVersion == 2, "Update code");
|
||||
stream.Write(HeaderVersion);
|
||||
stream.Write(modelData.MinScreenSize);
|
||||
|
||||
// Materials
|
||||
stream.WriteInt32(modelData.Materials.Count());
|
||||
for (const auto& slot : modelData.Materials)
|
||||
{
|
||||
stream.Write(slot.AssetID);
|
||||
stream.Write((byte)slot.ShadowsMode);
|
||||
stream.Write(slot.Name, 11);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveLOD(WriteStream& stream, int32 lodIndex) const
|
||||
{
|
||||
// Download all meshes buffers from the GPU
|
||||
Array<Task*> tasks;
|
||||
Array<const MeshBase*> meshes;
|
||||
GetMeshes(meshes, lodIndex);
|
||||
const int32 meshesCount = meshes.Count();
|
||||
struct MeshData
|
||||
{
|
||||
BytesContainer VB[3];
|
||||
BytesContainer IB;
|
||||
};
|
||||
Array<MeshData> meshesData;
|
||||
meshesData.Resize(meshesCount);
|
||||
tasks.EnsureCapacity(meshesCount * 4);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = *meshes[meshIndex];
|
||||
auto& meshData = meshesData[meshIndex];
|
||||
|
||||
// Vertex Buffer 0 (required)
|
||||
auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, meshData.VB[0]);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
|
||||
// Vertex Buffer 1 (optional)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex1, meshData.VB[1]);
|
||||
if (task)
|
||||
{
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Vertex Buffer 2 (optional)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex2, meshData.VB[2]);
|
||||
if (task)
|
||||
{
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Index Buffer (required)
|
||||
task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, meshData.IB);
|
||||
if (task == nullptr)
|
||||
return true;
|
||||
task->Start();
|
||||
tasks.Add(task);
|
||||
}
|
||||
|
||||
// Wait for async tasks
|
||||
if (Task::WaitAll(tasks))
|
||||
return true;
|
||||
|
||||
// Create meshes data
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
stream.Write(MeshVersion);
|
||||
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
|
||||
{
|
||||
const auto& mesh = *meshes[meshIndex];
|
||||
const auto& meshData = meshesData[meshIndex];
|
||||
uint32 vertices = mesh.GetVertexCount();
|
||||
uint32 triangles = mesh.GetTriangleCount();
|
||||
uint32 indicesCount = triangles * 3;
|
||||
bool shouldUse16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
bool use16BitIndexBuffer = mesh.Use16BitIndexBuffer();
|
||||
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
|
||||
uint32 ibSize = indicesCount * ibStride;
|
||||
|
||||
// Validate data
|
||||
if (vertices == 0 || triangles == 0)
|
||||
{
|
||||
LOG(Warning, "Cannot save model with empty meshes.");
|
||||
return true;
|
||||
}
|
||||
Array<const GPUVertexLayout*, FixedAllocation<MODEL_MAX_VB>> vbLayout;
|
||||
for (int32 vbIndex = 0; vbIndex < MODEL_MAX_VB; vbIndex++)
|
||||
{
|
||||
if (vbIndex != 0 && meshData.VB[vbIndex].IsInvalid()) // VB0 is always required
|
||||
continue;
|
||||
const GPUBuffer* vb = mesh.GetVertexBuffer(vbIndex);
|
||||
if (!vb)
|
||||
break;
|
||||
const GPUVertexLayout* layout = vb->GetVertexLayout();
|
||||
if (!layout)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer {}. Missing vertex layout.", vbIndex);
|
||||
return true;
|
||||
}
|
||||
const uint32 vbSize = vb->GetSize();
|
||||
vbLayout.Add(layout);
|
||||
if ((uint32)meshData.VB[vbIndex].Length() != vbSize)
|
||||
{
|
||||
LOG(Warning, "Invalid vertex buffer {} size. Got {} bytes but expected {} bytes. Stride: {}. Layout:\n{}", vbIndex, meshData.VB[vbIndex].Length(), vbSize, vb->GetStride(), layout->GetElementsString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
if ((uint32)meshData.IB.Length() < ibSize)
|
||||
{
|
||||
LOG(Warning, "Invalid index buffer size. Got {} bytes bytes expected {} bytes. Stride: {}", meshData.IB.Length(), ibSize, ibStride);
|
||||
return true;
|
||||
}
|
||||
|
||||
// Write descriptor
|
||||
stream.Write(vertices);
|
||||
stream.Write(triangles);
|
||||
byte vbCount = (byte)vbLayout.Count();
|
||||
stream.Write(vbCount);
|
||||
for (const GPUVertexLayout* layout : vbLayout)
|
||||
stream.Write(layout->GetElements());
|
||||
|
||||
// Write actual mesh data
|
||||
for (int32 vbIndex = 0; vbIndex < vbCount; vbIndex++)
|
||||
{
|
||||
const auto& vb = meshData.VB[vbIndex];
|
||||
stream.WriteBytes(vb.Get(), vb.Length());
|
||||
}
|
||||
if (shouldUse16BitIndexBuffer == use16BitIndexBuffer)
|
||||
{
|
||||
stream.WriteBytes(meshData.IB.Get(), ibSize);
|
||||
}
|
||||
else if (shouldUse16BitIndexBuffer)
|
||||
{
|
||||
// Convert 32-bit indices to 16-bit
|
||||
auto ib = (const int32*)meshData.IB.Get();
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
{
|
||||
stream.WriteUint16(ib[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
CRASH;
|
||||
}
|
||||
|
||||
// Write custom data
|
||||
if (SaveMesh(stream, &mesh))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveLOD(WriteStream& stream, const ModelData& modelData, int32 lodIndex, bool saveMesh(WriteStream& stream, const ModelData& modelData, int32 lodIndex, int32 meshIndex))
|
||||
{
|
||||
// Create meshes data
|
||||
static_assert(MeshVersion == 2, "Update code");
|
||||
stream.Write(MeshVersion);
|
||||
const auto& lod = modelData.LODs[lodIndex];
|
||||
for (int32 meshIndex = 0; meshIndex < lod.Meshes.Count(); meshIndex++)
|
||||
{
|
||||
const auto& mesh = *lod.Meshes[meshIndex];
|
||||
uint32 vertices = (uint32)mesh.Positions.Count();
|
||||
uint32 indicesCount = (uint32)mesh.Indices.Count();
|
||||
uint32 triangles = indicesCount / 3;
|
||||
bool use16BitIndexBuffer = indicesCount <= MAX_uint16;
|
||||
const bool isSkinned = mesh.BlendIndices.HasItems() && mesh.BlendWeights.HasItems();
|
||||
|
||||
// Validate data
|
||||
if (vertices == 0 || triangles == 0 || indicesCount % 3 != 0)
|
||||
{
|
||||
LOG(Warning, "Cannot save model with empty meshes.");
|
||||
return true;
|
||||
}
|
||||
bool hasUVs = mesh.UVs.HasItems();
|
||||
if (hasUVs && (uint32)mesh.UVs.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("UVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasNormals = mesh.Normals.HasItems();
|
||||
if (hasNormals && (uint32)mesh.Normals.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Normals"));
|
||||
return true;
|
||||
}
|
||||
bool hasTangents = mesh.Tangents.HasItems();
|
||||
if (hasTangents && (uint32)mesh.Tangents.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Tangents"));
|
||||
return true;
|
||||
}
|
||||
bool hasBitangentSigns = mesh.BitangentSigns.HasItems();
|
||||
if (hasBitangentSigns && (uint32)mesh.BitangentSigns.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BitangentSigns"));
|
||||
return true;
|
||||
}
|
||||
bool hasLightmapUVs = mesh.LightmapUVs.HasItems();
|
||||
if (hasLightmapUVs && (uint32)mesh.LightmapUVs.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("LightmapUVs"));
|
||||
return true;
|
||||
}
|
||||
bool hasColors = mesh.Colors.HasItems();
|
||||
if (hasColors && (uint32)mesh.Colors.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("Colors"));
|
||||
return true;
|
||||
}
|
||||
if (isSkinned && (uint32)mesh.BlendIndices.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendIndices"));
|
||||
return true;
|
||||
}
|
||||
if (isSkinned && (uint32)mesh.BlendWeights.Count() != vertices)
|
||||
{
|
||||
LOG(Error, "Invalid size of {0} stream.", TEXT("BlendWeights"));
|
||||
return true;
|
||||
}
|
||||
|
||||
// Define vertex buffers layout and packing
|
||||
Array<GPUVertexLayout::Elements, FixedAllocation<MODEL_MAX_VB>> vbElements;
|
||||
const bool useSeparatePositions = !isSkinned;
|
||||
const bool useSeparateColors = !isSkinned;
|
||||
{
|
||||
byte vbIndex = 0;
|
||||
// TODO: add option to quantize vertex positions (eg. 16-bit)
|
||||
// TODO: add option to quantize vertex attributes (eg. 8-bit blend weights, 8-bit texcoords)
|
||||
// TODO: add support for 16-bit blend indices (up to 65535 bones)
|
||||
|
||||
// Position
|
||||
if (useSeparatePositions)
|
||||
{
|
||||
auto& vb0 = vbElements.AddOne();
|
||||
vb0.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float });
|
||||
vbIndex++;
|
||||
}
|
||||
|
||||
// General purpose components
|
||||
{
|
||||
auto& vb = vbElements.AddOne();
|
||||
if (!useSeparatePositions)
|
||||
vb.Add({ VertexElement::Types::Position, vbIndex, 0, 0, PixelFormat::R32G32B32_Float });
|
||||
if (hasUVs)
|
||||
vb.Add({ VertexElement::Types::TexCoord, vbIndex, 0, 0, PixelFormat::R16G16_Float });
|
||||
if (hasLightmapUVs)
|
||||
vb.Add({ VertexElement::Types::TexCoord1, vbIndex, 0, 0, PixelFormat::R16G16_Float });
|
||||
vb.Add({ VertexElement::Types::Normal, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||
vb.Add({ VertexElement::Types::Tangent, vbIndex, 0, 0, PixelFormat::R10G10B10A2_UNorm });
|
||||
if (isSkinned)
|
||||
{
|
||||
vb.Add({ VertexElement::Types::BlendIndices, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UInt });
|
||||
vb.Add({ VertexElement::Types::BlendWeights, vbIndex, 0, 0, PixelFormat::R16G16B16A16_Float });
|
||||
}
|
||||
if (!useSeparateColors && hasColors)
|
||||
vb.Add({ VertexElement::Types::Color, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UNorm });
|
||||
vbIndex++;
|
||||
}
|
||||
|
||||
// Colors
|
||||
if (useSeparateColors && hasColors)
|
||||
{
|
||||
auto& vb = vbElements.AddOne();
|
||||
vb.Add({ VertexElement::Types::Color, vbIndex, 0, 0, PixelFormat::R8G8B8A8_UNorm });
|
||||
vbIndex++;
|
||||
}
|
||||
}
|
||||
|
||||
// Write descriptor
|
||||
stream.Write(vertices);
|
||||
stream.Write(triangles);
|
||||
byte vbCount = (byte)vbElements.Count();
|
||||
stream.Write(vbCount);
|
||||
for (const auto& elements : vbElements)
|
||||
stream.Write(elements);
|
||||
|
||||
// Write vertex buffers
|
||||
for (int32 vbIndex = 0; vbIndex < vbCount; vbIndex++)
|
||||
{
|
||||
if (useSeparatePositions && vbIndex == 0)
|
||||
{
|
||||
// Fast path for vertex positions of static models using the first buffer
|
||||
stream.WriteBytes(mesh.Positions.Get(), sizeof(Float3) * vertices);
|
||||
continue;
|
||||
}
|
||||
|
||||
// Write vertex components interleaved
|
||||
const GPUVertexLayout::Elements& layout = vbElements[vbIndex];
|
||||
for (uint32 vertex = 0; vertex < vertices; vertex++)
|
||||
{
|
||||
for (const VertexElement& element : layout)
|
||||
{
|
||||
switch (element.Type)
|
||||
{
|
||||
case VertexElement::Types::Position:
|
||||
{
|
||||
const Float3 position = mesh.Positions.Get()[vertex];
|
||||
stream.Write(position);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::Color:
|
||||
{
|
||||
const Color32 color(mesh.Colors.Get()[vertex]);
|
||||
stream.Write(color);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::Normal:
|
||||
{
|
||||
const Float3 normal = hasNormals ? mesh.Normals.Get()[vertex] : Float3::UnitZ;
|
||||
const FloatR10G10B10A2 normalEnc(normal * 0.5f + 0.5f, 0);
|
||||
stream.Write(normalEnc.Value);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::Tangent:
|
||||
{
|
||||
const Float3 normal = hasNormals ? mesh.Normals.Get()[vertex] : Float3::UnitZ;
|
||||
const Float3 tangent = hasTangents ? mesh.Tangents.Get()[vertex] : Float3::UnitX;
|
||||
const float bitangentSign = hasBitangentSigns ? mesh.BitangentSigns.Get()[vertex] : Float3::Dot(Float3::Cross(Float3::Normalize(Float3::Cross(normal, tangent)), normal), tangent);
|
||||
const Float1010102 tangentEnc(tangent * 0.5f + 0.5f, (byte)(bitangentSign < 0 ? 1 : 0));
|
||||
stream.Write(tangentEnc.Value);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::BlendIndices:
|
||||
{
|
||||
const Int4 blendIndices = mesh.BlendIndices.Get()[vertex];
|
||||
const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
|
||||
stream.Write(blendIndicesEnc);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::BlendWeights:
|
||||
{
|
||||
const Float4 blendWeights = mesh.BlendWeights.Get()[vertex];
|
||||
const Half4 blendWeightsEnc(blendWeights);
|
||||
stream.Write(blendWeightsEnc);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::TexCoord0:
|
||||
{
|
||||
const Float2 uv = hasUVs ? mesh.UVs.Get()[vertex] : Float2::Zero;
|
||||
const Half2 uvEnc(uv);
|
||||
stream.Write(uvEnc);
|
||||
break;
|
||||
}
|
||||
case VertexElement::Types::TexCoord1:
|
||||
{
|
||||
// TODO: refactor LightmapUVs into a generic TexCoord channel and support up to 4 UVs
|
||||
const Float2 lightmapUV = hasLightmapUVs ? mesh.LightmapUVs.Get()[vertex] : Float2::Zero;
|
||||
const Half2 lightmapUVEnc(lightmapUV);
|
||||
stream.Write(lightmapUVEnc);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
LOG(Error, "Unsupported vertex element: {}", element.ToString());
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Write index buffer
|
||||
const uint32* meshIndices = mesh.Indices.Get();
|
||||
if (use16BitIndexBuffer)
|
||||
{
|
||||
for (uint32 i = 0; i < indicesCount; i++)
|
||||
stream.Write((uint16)meshIndices[i]);
|
||||
}
|
||||
else
|
||||
{
|
||||
stream.WriteBytes(meshIndices, sizeof(uint32) * indicesCount);
|
||||
}
|
||||
|
||||
// Write custom data
|
||||
if (saveMesh && saveMesh(stream, modelData, lodIndex, meshIndex))
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::SaveMesh(WriteStream& stream, const MeshBase* mesh) const
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
bool ModelBase::Save(bool withMeshDataFromGpu, Function<FlaxChunk*(int32)>& getChunk)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
void ModelBase::CancelStreaming()
|
||||
{
|
||||
BinaryAsset::CancelStreaming();
|
||||
|
||||
Reference in New Issue
Block a user