Refactor models and meshes to share more code in a base class
This commit is contained in:
311
Source/Engine/Content/Assets/ModelBase.cpp
Normal file
311
Source/Engine/Content/Assets/ModelBase.cpp
Normal file
@@ -0,0 +1,311 @@
|
||||
// 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"
|
||||
#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
|
||||
#include "SkinnedModel.h" // TODO: remove this
|
||||
#include "Model.h" // TODO: remove this
|
||||
|
||||
/// <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());
|
||||
|
||||
// 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)
|
||||
Array<MeshBase*> meshes;
|
||||
model->GetMeshes(meshes, _lodIndex);
|
||||
if (model->Is<SkinnedModel>())
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
for (int32 i = 0; i < meshes.Count(); i++)
|
||||
{
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Update residency level
|
||||
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();
|
||||
}
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
Reference in New Issue
Block a user