Files
FlaxEngine/Source/Engine/Content/Assets/ModelBase.cpp

901 lines
29 KiB
C++

// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "ModelBase.h"
#include "Engine/Core/Log.h"
#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
#else
#include "Engine/Threading/MainThreadTask.h"
#define STREAM_TASK_BASE MainThreadTask
#endif
#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.
/// </summary>
class StreamModelLODTask : public STREAM_TASK_BASE
{
private:
WeakAssetReference<ModelBase> _model;
int32 _lodIndex;
FlaxStorage::LockData _dataLock;
public:
StreamModelLODTask(ModelBase* model, int32 lodIndex)
: _model(model)
, _lodIndex(lodIndex)
, _dataLock(model->Storage->Lock())
{
}
bool HasReference(Object* resource) const override
{
return _model == resource;
}
bool Run() override
{
AssetReference<ModelBase> model = _model.Get();
if (model == nullptr)
return true;
// Get data
BytesContainer data;
model->GetLODData(_lodIndex, data);
if (data.IsInvalid())
{
LOG(Warning, "Missing data chunk");
return true;
}
MemoryReadStream stream(data.Get(), data.Length());
// Load meshes data and pass data to the GPU buffers
Array<MeshBase*> meshes;
model->GetMeshes(meshes, _lodIndex);
byte meshVersion = stream.ReadByte();
if (meshVersion < 2 || meshVersion > ModelBase::MeshVersion)
{
LOG(Warning, "Unsupported mesh version {}", meshVersion);
return true;
}
for (int32 meshIndex = 0; meshIndex < meshes.Count(); meshIndex++)
{
if (model->LoadMesh(stream, meshVersion, meshes[meshIndex]))
{
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();
return false;
}
void OnEnd() override
{
// Unlink
ModelBase* model = _model.Get();
if (model)
{
ASSERT(model->_streamingTask == this);
model->_streamingTask = nullptr;
_model = nullptr;
}
_dataLock.Release();
STREAM_TASK_BASE::OnEnd();
}
};
ModelBase::~ModelBase()
{
ASSERT(_streamingTask == nullptr);
}
void ModelBase::SetupMaterialSlots(int32 slotsCount)
{
CHECK(slotsCount >= 0 && slotsCount < 4096);
if (!IsVirtual() && WaitForLoaded())
return;
ScopeLock lock(Locker);
const int32 prevCount = MaterialSlots.Count();
MaterialSlots.Resize(slotsCount, false);
// Initialize slot names
for (int32 i = prevCount; i < slotsCount; i++)
MaterialSlots[i].Name = String::Format(TEXT("Material {0}"), i + 1);
}
MaterialSlot* ModelBase::GetSlot(const StringView& name)
{
MaterialSlot* result = nullptr;
for (auto& slot : MaterialSlots)
{
if (slot.Name == name)
{
result = &slot;
break;
}
}
return result;
}
ContentLoadTask* ModelBase::RequestLODDataAsync(int32 lodIndex)
{
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
return RequestChunkDataAsync(chunkIndex);
}
void ModelBase::GetLODData(int32 lodIndex, BytesContainer& data) const
{
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
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;
}
for (auto& channel : mesh.UVs)
{
if ((uint32)channel.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 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;
PixelFormat blendIndicesFormat = PixelFormat::R8G8B8A8_UInt;
for (const Int4& indices : mesh.BlendIndices)
{
if (indices.MaxValue() > MAX_uint8)
{
blendIndicesFormat = PixelFormat::R16G16B16A16_UInt;
break;
}
}
{
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)
// 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 });
for (int32 channelIdx = 0; channelIdx < mesh.UVs.Count(); channelIdx++)
{
auto& channel = mesh.UVs.Get()[channelIdx];
if (channel.HasItems())
{
vb.Add({ (VertexElement::Types)((int32)VertexElement::Types::TexCoord0 + channelIdx), 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, blendIndicesFormat });
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];
if (blendIndicesFormat == PixelFormat::R8G8B8A8_UInt)
{
// 8-bit indices
const Color32 blendIndicesEnc(blendIndices.X, blendIndices.Y, blendIndices.Z, blendIndices.W);
stream.Write(blendIndicesEnc);
}
else
{
// 16-bit indices
const uint16 blendIndicesEnc[4] = { (uint16)blendIndices.X, (uint16)blendIndices.Y, (uint16)blendIndices.Z, (uint16)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:
case VertexElement::Types::TexCoord1:
case VertexElement::Types::TexCoord2:
case VertexElement::Types::TexCoord3:
{
const int32 channelIdx = (int32)element.Type - (int32)VertexElement::Types::TexCoord0;
const Float2 uv = mesh.UVs.Get()[channelIdx].Get()[vertex];
const Half2 uvEnc(uv);
stream.Write(uvEnc);
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();
CancelStreamingTasks();
}
#if USE_EDITOR
void ModelBase::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
BinaryAsset::GetReferences(assets, files);
for (auto& slot : MaterialSlots)
assets.Add(slot.Material.GetID());
}
#endif
int32 ModelBase::GetCurrentResidency() const
{
return _loadedLODs;
}
bool ModelBase::CanBeUpdated() const
{
// Check if is ready and has no streaming tasks running
return IsInitialized() && _streamingTask == nullptr;
}
Task* ModelBase::UpdateAllocation(int32 residency)
{
// Models are not using dynamic allocation feature
return nullptr;
}
Task* ModelBase::CreateStreamingTask(int32 residency)
{
ScopeLock lock(Locker);
const int32 lodMax = GetLODsCount();
ASSERT(IsInitialized() && Math::IsInRange(residency, 0, lodMax) && _streamingTask == nullptr);
Task* result = nullptr;
const int32 lodCount = residency - GetCurrentResidency();
// Switch if go up or down with residency
if (lodCount > 0)
{
// Allow only to change LODs count by 1
ASSERT(Math::Abs(lodCount) == 1);
int32 lodIndex = HighestResidentLODIndex() - 1;
// Request LOD data
result = (Task*)RequestLODDataAsync(lodIndex);
// Add upload data task
_streamingTask = New<StreamModelLODTask>(this, lodIndex);
if (result)
result->ContinueWith(_streamingTask);
else
result = _streamingTask;
}
else
{
// Do the quick data release
Array<MeshBase*> meshes;
for (int32 i = HighestResidentLODIndex(); i < lodMax - residency; i++)
{
GetMeshes(meshes, i);
for (MeshBase* mesh : meshes)
mesh->Release();
}
_loadedLODs = residency;
ResidencyChanged();
}
return result;
}
void ModelBase::CancelStreamingTasks()
{
if (_streamingTask)
{
_streamingTask->Cancel();
ASSERT_LOW_LAYER(_streamingTask == nullptr);
}
}
void ModelBase::unload(bool isReloading)
{
// End streaming (if still active)
if (_streamingTask != nullptr)
{
// Cancel streaming task
_streamingTask->Cancel();
_streamingTask = nullptr;
}
// Cleanup
MaterialSlots.Resize(0);
_initialized = false;
_loadedLODs = 0;
}