Refactor models and meshes to share more code in a base class

This commit is contained in:
Wojtek Figat
2024-12-19 00:20:08 +01:00
parent 8eaa906e0c
commit 1bf29c042b
18 changed files with 842 additions and 1197 deletions

View File

@@ -16,101 +16,16 @@
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Graphics/Async/GPUTask.h"
#include "Engine/Graphics/Async/Tasks/GPUUploadTextureMipTask.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Graphics/Textures/GPUTexture.h"
#include "Engine/Graphics/Textures/TextureData.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/DrawCall.h"
#include "Engine/Threading/Threading.h"
#include "Engine/Tools/ModelTool/ModelTool.h"
#include "Engine/Tools/ModelTool/MeshAccelerationStructure.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
#define CHECK_INVALID_BUFFER(model, buffer) \
if (buffer->IsValidFor(model) == false) \
{ \
buffer->Setup(model); \
}
REGISTER_BINARY_ASSET_ABSTRACT(ModelBase, "FlaxEngine.ModelBase");
/// <summary>
/// Model LOD streaming task.
/// </summary>
class StreamModelLODTask : public STREAM_TASK_BASE
{
private:
WeakAssetReference<Model> _asset;
int32 _lodIndex;
FlaxStorage::LockData _dataLock;
public:
StreamModelLODTask(Model* model, int32 lodIndex)
: _asset(model)
, _lodIndex(lodIndex)
, _dataLock(model->Storage->Lock())
{
}
public:
bool HasReference(Object* resource) const override
{
return _asset == resource;
}
bool Run() override
{
AssetReference<Model> model = _asset.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)
if (model->LODs[_lodIndex].Load(stream))
{
LOG(Warning, "Cannot load LOD{1} for model \'{0}\'", model->ToString(), _lodIndex);
return true;
}
// Update residency level
model->_loadedLODs++;
model->ResidencyChanged();
return false;
}
void OnEnd() override
{
// Unlink
if (_asset)
{
ASSERT(_asset->_streamingTask == this);
_asset->_streamingTask = nullptr;
_asset = nullptr;
}
_dataLock.Release();
// Base
STREAM_TASK_BASE::OnEnd();
}
};
class StreamModelSDFTask : public GPUUploadTextureMipTask
{
private:
@@ -211,9 +126,10 @@ FORCE_INLINE void ModelDraw(Model* model, const RenderContext& renderContext, co
ASSERT(info.Buffer);
if (!model->CanBeRendered())
return;
if (!info.Buffer->IsValidFor(model))
info.Buffer->Setup(model);
const auto frame = Engine::FrameCount;
const auto modelFrame = info.DrawState->PrevFrame + 1;
CHECK_INVALID_BUFFER(model, info.Buffer);
// Select a proper LOD index (model may be culled)
int32 lodIndex;
@@ -719,16 +635,14 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
SAFE_DELETE_GPU_RESOURCE(SDF.Texture);
// Setup LODs
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
LODs[lodIndex].Dispose();
LODs.Resize(meshesCountPerLod.Length());
_initialized = true;
// Setup meshes
for (int32 lodIndex = 0; lodIndex < meshesCountPerLod.Length(); lodIndex++)
{
auto& lod = LODs[lodIndex];
lod._model = this;
lod._lodIndex = lodIndex;
lod.Link(this, lodIndex);
lod.ScreenSize = 1.0f;
const int32 meshesCount = meshesCountPerLod[lodIndex];
if (meshesCount <= 0 || meshesCount > MODEL_MAX_MESHES)
@@ -737,7 +651,7 @@ bool Model::Init(const Span<int32>& meshesCountPerLod)
lod.Meshes.Resize(meshesCount);
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
lod.Meshes[meshIndex].Init(this, lodIndex, meshIndex, 0, BoundingBox::Zero, BoundingSphere::Empty, true);
lod.Meshes[meshIndex].Link(this, lodIndex, meshIndex);
}
}
@@ -787,99 +701,16 @@ void Model::InitAsVirtual()
BinaryAsset::InitAsVirtual();
}
void Model::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}
#if USE_EDITOR
void Model::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
// Base
BinaryAsset::GetReferences(assets, files);
for (int32 i = 0; i < MaterialSlots.Count(); i++)
assets.Add(MaterialSlots[i].Material.GetID());
}
#endif
int32 Model::GetMaxResidency() const
{
return LODs.Count();
}
int32 Model::GetCurrentResidency() const
{
return _loadedLODs;
}
int32 Model::GetAllocatedResidency() const
{
return LODs.Count();
}
bool Model::CanBeUpdated() const
{
// Check if is ready and has no streaming tasks running
return IsInitialized() && _streamingTask == nullptr;
}
Task* Model::UpdateAllocation(int32 residency)
{
// Models are not using dynamic allocation feature
return nullptr;
}
Task* Model::CreateStreamingTask(int32 residency)
{
ScopeLock lock(Locker);
ASSERT(IsInitialized() && Math::IsInRange(residency, 0, LODs.Count()) && _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
for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++)
LODs[i].Unload();
_loadedLODs = residency;
ResidencyChanged();
}
return result;
}
void Model::CancelStreamingTasks()
{
if (_streamingTask)
{
_streamingTask->Cancel();
ASSERT_LOW_LAYER(_streamingTask == nullptr);
}
}
Asset::LoadResult Model::load()
{
// Get header chunk
@@ -922,6 +753,7 @@ Asset::LoadResult Model::load()
if (lods == 0 || lods > MODEL_MAX_LODS)
return LoadResult::InvalidData;
LODs.Resize(lods);
_initialized = true;
// For each LOD
for (int32 lodIndex = 0; lodIndex < lods; lodIndex++)
@@ -946,6 +778,9 @@ Asset::LoadResult Model::load()
// For each mesh
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
Mesh& mesh = lod.Meshes[meshIndex];
mesh.Link(this, lodIndex, meshIndex);
// Material Slot index
int32 materialSlotIndex;
stream->ReadInt32(&materialSlotIndex);
@@ -954,19 +789,18 @@ Asset::LoadResult Model::load()
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount);
return LoadResult::InvalidData;
}
mesh.SetMaterialSlotIndex(materialSlotIndex);
// Box
// Bounds
BoundingBox box;
stream->ReadBoundingBox(&box);
// Sphere
BoundingSphere sphere;
stream->ReadBoundingSphere(&sphere);
mesh.SetBounds(box, sphere);
// Has Lightmap UVs
bool hasLightmapUVs = stream->ReadBool();
lod.Meshes[meshIndex].Init(this, lodIndex, meshIndex, materialSlotIndex, box, sphere, hasLightmapUVs);
mesh.LightmapUVsIndex = hasLightmapUVs ? 1 : -1;
}
}
@@ -1039,33 +873,11 @@ Asset::LoadResult Model::load()
void Model::unload(bool isReloading)
{
// End streaming (if still active)
if (_streamingTask != nullptr)
{
// Cancel streaming task
_streamingTask->Cancel();
_streamingTask = nullptr;
}
ModelBase::unload(isReloading);
// Cleanup
SAFE_DELETE_GPU_RESOURCE(SDF.Texture);
MaterialSlots.Resize(0);
for (int32 i = 0; i < LODs.Count(); i++)
LODs[i].Dispose();
LODs.Clear();
_loadedLODs = 0;
}
bool Model::init(AssetInitData& initData)
{
// Validate
if (initData.SerializedVersion != SerializedVersion)
{
LOG(Error, "Invalid serialized model version.");
return true;
}
return false;
}
AssetChunksFlag Model::getChunksToPreload() const
@@ -1074,32 +886,101 @@ AssetChunksFlag Model::getChunksToPreload() const
return GET_CHUNK_FLAG(0) | GET_CHUNK_FLAG(15);
}
void ModelBase::SetupMaterialSlots(int32 slotsCount)
bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh)
{
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)
bool result = false;
Real closest = MAX_Real;
Vector3 closestNormal = Vector3::Up;
for (int32 i = 0; i < Meshes.Count(); i++)
{
if (slot.Name == name)
Real dst;
Vector3 nrm;
if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest)
{
result = &slot;
break;
result = true;
*mesh = &Meshes[i];
closest = dst;
closestNormal = nrm;
}
}
distance = closest;
normal = closestNormal;
return result;
}
bool ModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, Mesh** mesh)
{
bool result = false;
Real closest = MAX_Real;
Vector3 closestNormal = Vector3::Up;
for (int32 i = 0; i < Meshes.Count(); i++)
{
Real dst;
Vector3 nrm;
if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest)
{
result = true;
*mesh = &Meshes[i];
closest = dst;
closestNormal = nrm;
}
}
distance = closest;
normal = closestNormal;
return result;
}
BoundingBox ModelLOD::GetBox(const Matrix& world) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[meshIndex];
mesh.GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
Vector3::Transform(corners[i], world, tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[meshIndex];
BoundingBox box = mesh.GetBox();
if (deformation)
deformation->GetBounds(_lodIndex, meshIndex, box);
box.GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
transform.LocalToWorld(corners[i], tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
BoundingBox ModelLOD::GetBox() const
{
Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
Meshes[meshIndex].GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
min = Vector3::Min(min, corners[i]);
max = Vector3::Max(max, corners[i]);
}
}
return BoundingBox(min, max);
}

View File

@@ -6,7 +6,6 @@
#include "Engine/Graphics/Models/ModelLOD.h"
class Mesh;
class StreamModelLODTask;
/// <summary>
/// Model asset that contains model object made of meshes which can rendered on the GPU.
@@ -15,10 +14,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Model : public ModelBase
{
DECLARE_BINARY_ASSET_HEADER(Model, 25);
friend Mesh;
friend StreamModelLODTask;
private:
int32 _loadedLODs = 0;
StreamModelLODTask* _streamingTask = nullptr;
public:
/// <summary>
@@ -38,40 +33,6 @@ public:
~Model();
public:
/// <summary>
/// Gets a value indicating whether this instance is initialized.
/// </summary>
FORCE_INLINE bool IsInitialized() const
{
return LODs.HasItems();
}
/// <summary>
/// Gets the amount of loaded model LODs.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const
{
return _loadedLODs;
}
/// <summary>
/// Clamps the index of the LOD to be valid for rendering (only loaded LODs).
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The resident LOD index.</returns>
FORCE_INLINE int32 ClampLODIndex(int32 index) const
{
return Math::Clamp(index, HighestResidentLODIndex(), LODs.Count() - 1);
}
/// <summary>
/// Gets index of the highest resident LOD (may be equal to LODs.Count if no LOD has been uploaded). Note: LOD=0 is the highest (top quality)
/// </summary>
FORCE_INLINE int32 HighestResidentLODIndex() const
{
return LODs.Count() - _loadedLODs;
}
/// <summary>
/// Determines whether any LOD has been initialized.
/// </summary>
@@ -80,37 +41,6 @@ public:
return LODs.HasItems() && LODs.Last().HasAnyMeshInitialized();
}
/// <summary>
/// Determines whether this model can be rendered.
/// </summary>
FORCE_INLINE bool CanBeRendered() const
{
return _loadedLODs > 0;
}
public:
/// <summary>
/// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here).
/// </summary>
/// <param name="lodIndex">Index of the LOD.</param>
/// <returns>Task that will gather chunk data or null if already here.</returns>
ContentLoadTask* RequestLODDataAsync(int32 lodIndex)
{
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
return RequestChunkDataAsync(chunkIndex);
}
/// <summary>
/// Gets the model LOD data (links bytes).
/// </summary>
/// <param name="lodIndex">Index of the LOD.</param>
/// <param name="data">The data (may be missing if failed to get it).</param>
void GetLODData(int32 lodIndex, BytesContainer& data) const
{
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
GetChunkData(chunkIndex, data);
}
public:
/// <summary>
/// Determines if there is an intersection between the Model and a Ray in given world using given instance.
@@ -250,24 +180,14 @@ public:
int32 GetLODsCount() const override;
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
void InitAsVirtual() override;
void CancelStreaming() override;
#if USE_EDITOR
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
// [StreamableResource]
int32 GetMaxResidency() const override;
int32 GetCurrentResidency() const override;
int32 GetAllocatedResidency() const override;
bool CanBeUpdated() const override;
Task* UpdateAllocation(int32 residency) override;
Task* CreateStreamingTask(int32 residency) override;
void CancelStreamingTasks() override;
protected:
// [ModelBase]
LoadResult load() override;
void unload(bool isReloading) override;
bool init(AssetInitData& initData) override;
AssetChunksFlag getChunksToPreload() const override;
};

View 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;
}

View File

@@ -4,6 +4,7 @@
#include "../BinaryAsset.h"
#include "Engine/Core/Collections/Array.h"
#include "Engine/Graphics/Models/Config.h"
#include "Engine/Graphics/Models/MaterialSlot.h"
#include "Engine/Streaming/StreamableResource.h"
@@ -26,6 +27,14 @@ struct RenderContextBatch;
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API ModelBase : public BinaryAsset, public StreamableResource
{
DECLARE_ASSET_HEADER(ModelBase);
friend MeshBase;
friend class StreamModelLODTask;
protected:
bool _initialized = false;
int32 _loadedLODs = 0;
StreamModelLODTask* _streamingTask = nullptr;
public:
/// <summary>
/// The Sign Distant Field (SDF) data for the model.
@@ -106,6 +115,48 @@ public:
return MaterialSlots.Count();
}
/// <summary>
/// Gets a value indicating whether this instance is initialized.
/// </summary>
FORCE_INLINE bool IsInitialized() const
{
return _initialized;
}
/// <summary>
/// Clamps the index of the LOD to be valid for rendering (only loaded LODs).
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The resident LOD index.</returns>
FORCE_INLINE int32 ClampLODIndex(int32 index) const
{
return Math::Clamp(index, HighestResidentLODIndex(), GetLODsCount() - 1);
}
/// <summary>
/// Gets index of the highest resident LOD (it may be equal to LODs.Count if no LOD has been uploaded). Note: LOD=0 is the highest (top quality)
/// </summary>
FORCE_INLINE int32 HighestResidentLODIndex() const
{
return GetLODsCount() - _loadedLODs;
}
/// <summary>
/// Gets the amount of loaded model LODs.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const
{
return _loadedLODs;
}
/// <summary>
/// Determines whether this model can be rendered.
/// </summary>
FORCE_INLINE bool CanBeRendered() const
{
return _loadedLODs > 0;
}
/// <summary>
/// Resizes the material slots collection. Updates meshes that were using removed slots.
/// </summary>
@@ -127,4 +178,37 @@ public:
/// Gets the meshes for a particular LOD index.
/// </summary>
virtual void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) = 0;
public:
/// <summary>
/// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here).
/// </summary>
/// <param name="lodIndex">Index of the LOD.</param>
/// <returns>Task that will gather chunk data or null if already here.</returns>
ContentLoadTask* RequestLODDataAsync(int32 lodIndex);
/// <summary>
/// Gets the model LOD data (links bytes).
/// </summary>
/// <param name="lodIndex">Index of the LOD.</param>
/// <param name="data">The data (it may be missing if failed to get it).</param>
void GetLODData(int32 lodIndex, BytesContainer& data) const;
public:
// [BinaryAsset]
void CancelStreaming() override;
#if USE_EDITOR
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
// [StreamableResource]
int32 GetCurrentResidency() const override;
bool CanBeUpdated() const override;
Task* UpdateAllocation(int32 residency) override;
Task* CreateStreamingTask(int32 residency) override;
void CancelStreamingTasks() override;
protected:
// [BinaryAsset]
void unload(bool isReloading) override;
};

View File

@@ -13,101 +13,13 @@
#include "Engine/Graphics/Models/ModelInstanceEntry.h"
#include "Engine/Graphics/Models/Config.h"
#include "Engine/Content/Content.h"
#include "Engine/Content/WeakAssetReference.h"
#include "Engine/Content/Factories/BinaryAssetFactory.h"
#include "Engine/Content/Upgraders/SkinnedModelAssetUpgrader.h"
#include "Engine/Debug/Exceptions/ArgumentOutOfRangeException.h"
#include "Engine/Graphics/Models/MeshDeformation.h"
#include "Engine/Profiler/ProfilerCPU.h"
#include "Engine/Renderer/DrawCall.h"
#define CHECK_INVALID_BUFFER(model, buffer) \
if (buffer->IsValidFor(model) == false) \
{ \
buffer->Setup(model); \
}
/// <summary>
/// Skinned model data streaming task
/// </summary>
class StreamSkinnedModelLODTask : public ThreadPoolTask
{
private:
WeakAssetReference<SkinnedModel> _asset;
int32 _lodIndex;
FlaxStorage::LockData _dataLock;
public:
/// <summary>
/// Init
/// </summary>
/// <param name="model">Parent model</param>
/// <param name="lodIndex">LOD to stream index</param>
StreamSkinnedModelLODTask(SkinnedModel* model, int32 lodIndex)
: _asset(model)
, _lodIndex(lodIndex)
, _dataLock(model->Storage->Lock())
{
}
public:
// [ThreadPoolTask]
bool HasReference(Object* resource) const override
{
return _asset == resource;
}
protected:
// [ThreadPoolTask]
bool Run() override
{
AssetReference<SkinnedModel> model = _asset.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)
if (model->LODs[_lodIndex].Load(stream))
{
LOG(Warning, "Cannot load LOD{1} for model \'{0}\'", model->ToString(), _lodIndex);
return true;
}
// Update residency level
model->_loadedLODs++;
model->ResidencyChanged();
return false;
}
void OnEnd() override
{
// Unlink
if (_asset)
{
ASSERT(_asset->_streamingTask == this);
_asset->_streamingTask = nullptr;
_asset = nullptr;
}
_dataLock.Release();
// Base
ThreadPoolTask::OnEnd();
}
};
REGISTER_BINARY_ASSET_WITH_UPGRADER(SkinnedModel, "FlaxEngine.SkinnedModel", SkinnedModelAssetUpgrader, true);
SkinnedModel::SkinnedModel(const SpawnParams& params, const AssetInfo* info)
@@ -143,18 +55,6 @@ Array<String> SkinnedModel::GetBlendShapes()
return result;
}
ContentLoadTask* SkinnedModel::RequestLODDataAsync(int32 lodIndex)
{
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
return RequestChunkDataAsync(chunkIndex);
}
void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const
{
const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex);
GetChunkData(chunkIndex, data);
}
SkinnedModel::SkeletonMapping SkinnedModel::GetSkeletonMapping(Asset* source, bool autoRetarget)
{
SkeletonMapping mapping;
@@ -328,9 +228,10 @@ FORCE_INLINE void SkinnedModelDraw(SkinnedModel* model, const RenderContext& ren
ASSERT(info.Buffer);
if (!model->CanBeRendered())
return;
if (!info.Buffer->IsValidFor(model))
info.Buffer->Setup(model);
const auto frame = Engine::FrameCount;
const auto modelFrame = info.DrawState->PrevFrame + 1;
CHECK_INVALID_BUFFER(model, info.Buffer);
// Select a proper LOD index (model may be culled)
int32 lodIndex;
@@ -846,8 +747,6 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
// Setup
MaterialSlots.Resize(1);
MinScreenSize = 0.0f;
for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++)
LODs[lodIndex].Dispose();
LODs.Resize(meshesCountPerLod.Length());
_initialized = true;
@@ -865,7 +764,7 @@ bool SkinnedModel::Init(const Span<int32>& meshesCountPerLod)
lod.Meshes.Resize(meshesCount);
for (int32 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
lod.Meshes[meshIndex].Init(this, lodIndex, meshIndex, 0, BoundingBox::Zero, BoundingSphere::Empty);
lod.Meshes[meshIndex].Link(this, lodIndex, meshIndex);
}
}
@@ -971,99 +870,16 @@ void SkinnedModel::InitAsVirtual()
BinaryAsset::InitAsVirtual();
}
void SkinnedModel::CancelStreaming()
{
Asset::CancelStreaming();
CancelStreamingTasks();
}
#if USE_EDITOR
void SkinnedModel::GetReferences(Array<Guid>& assets, Array<String>& files) const
{
// Base
BinaryAsset::GetReferences(assets, files);
for (int32 i = 0; i < MaterialSlots.Count(); i++)
assets.Add(MaterialSlots[i].Material.GetID());
}
#endif
int32 SkinnedModel::GetMaxResidency() const
{
return LODs.Count();
}
int32 SkinnedModel::GetCurrentResidency() const
{
return _loadedLODs;
}
int32 SkinnedModel::GetAllocatedResidency() const
{
return LODs.Count();
}
bool SkinnedModel::CanBeUpdated() const
{
// Check if is ready and has no streaming tasks running
return IsInitialized() && _streamingTask == nullptr;
}
Task* SkinnedModel::UpdateAllocation(int32 residency)
{
// SkinnedModels are not using dynamic allocation feature
return nullptr;
}
Task* SkinnedModel::CreateStreamingTask(int32 residency)
{
ScopeLock lock(Locker);
ASSERT(IsInitialized() && Math::IsInRange(residency, 0, LODs.Count()) && _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<StreamSkinnedModelLODTask>(this, lodIndex);
if (result)
result->ContinueWith(_streamingTask);
else
result = _streamingTask;
}
else
{
// Do the quick data release
for (int32 i = HighestResidentLODIndex(); i < LODs.Count() - residency; i++)
LODs[i].Unload();
_loadedLODs = residency;
ResidencyChanged();
}
return result;
}
void SkinnedModel::CancelStreamingTasks()
{
if (_streamingTask)
{
_streamingTask->Cancel();
ASSERT_LOW_LAYER(_streamingTask == nullptr);
}
}
Asset::LoadResult SkinnedModel::load()
{
// Get header chunk
@@ -1134,7 +950,8 @@ Asset::LoadResult SkinnedModel::load()
// For each mesh
for (uint16 meshIndex = 0; meshIndex < meshesCount; meshIndex++)
{
auto& mesh = lod.Meshes[meshIndex];
SkinnedMesh& mesh = lod.Meshes[meshIndex];
mesh.Link(this, lodIndex, meshIndex);
// Material Slot index
int32 materialSlotIndex;
@@ -1144,17 +961,14 @@ Asset::LoadResult SkinnedModel::load()
LOG(Warning, "Invalid material slot index {0} for mesh {1}. Slots count: {2}.", materialSlotIndex, meshIndex, materialSlotsCount);
return LoadResult::InvalidData;
}
mesh.SetMaterialSlotIndex(materialSlotIndex);
// Box
// Bounds
BoundingBox box;
stream->ReadBoundingBox(&box);
// Sphere
BoundingSphere sphere;
stream->ReadBoundingSphere(&sphere);
// Create mesh object
mesh.Init(this, lodIndex, meshIndex, materialSlotIndex, box, sphere);
mesh.SetBounds(box, sphere);
// Blend Shapes
uint16 blendShapes;
@@ -1221,40 +1035,143 @@ Asset::LoadResult SkinnedModel::load()
void SkinnedModel::unload(bool isReloading)
{
// End streaming (if still active)
if (_streamingTask != nullptr)
{
// Cancel streaming task
_streamingTask->Cancel();
_streamingTask = nullptr;
}
ModelBase::unload(isReloading);
// Cleanup
MaterialSlots.Resize(0);
for (int32 i = 0; i < LODs.Count(); i++)
LODs[i].Dispose();
LODs.Clear();
Skeleton.Dispose();
_initialized = false;
_loadedLODs = 0;
_skeletonRetargets.Clear();
ClearSkeletonMapping();
}
bool SkinnedModel::init(AssetInitData& initData)
{
// Validate
if (initData.SerializedVersion != SerializedVersion)
{
LOG(Error, "Invalid serialized model version.");
return true;
}
return false;
}
AssetChunksFlag SkinnedModel::getChunksToPreload() const
{
// Note: we don't preload any meshes here because it's done by the Streaming Manager
return GET_CHUNK_FLAG(0);
}
bool SkinnedModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh)
{
// Check all meshes
bool result = false;
Real closest = MAX_float;
Vector3 closestNormal = Vector3::Up;
for (int32 i = 0; i < Meshes.Count(); i++)
{
// Test intersection with mesh and check if is closer than previous
Real dst;
Vector3 nrm;
if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest)
{
result = true;
*mesh = &Meshes[i];
closest = dst;
closestNormal = nrm;
}
}
distance = closest;
normal = closestNormal;
return result;
}
bool SkinnedModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh)
{
// Check all meshes
bool result = false;
Real closest = MAX_float;
Vector3 closestNormal = Vector3::Up;
for (int32 i = 0; i < Meshes.Count(); i++)
{
// Test intersection with mesh and check if is closer than previous
Real dst;
Vector3 nrm;
if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest)
{
result = true;
*mesh = &Meshes[i];
closest = dst;
closestNormal = nrm;
}
}
distance = closest;
normal = closestNormal;
return result;
}
BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const
{
// Find minimum and maximum points of all the meshes
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 j = 0; j < Meshes.Count(); j++)
{
const auto& mesh = Meshes[j];
mesh.GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
Vector3::Transform(corners[i], world, tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[meshIndex];
BoundingBox box = mesh.GetBox();
if (deformation)
deformation->GetBounds(_lodIndex, meshIndex, box);
box.GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
transform.LocalToWorld(corners[i], tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const
{
// Find minimum and maximum points of the mesh
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
const auto& mesh = Meshes[meshIndex];
mesh.GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
Vector3::Transform(corners[i], world, tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
return BoundingBox(min, max);
}
BoundingBox SkinnedModelLOD::GetBox() const
{
// Find minimum and maximum points of the mesh in given world
Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 j = 0; j < Meshes.Count(); j++)
{
Meshes[j].GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
min = Vector3::Min(min, corners[i]);
max = Vector3::Max(max, corners[i]);
}
}
return BoundingBox(min, max);
}

View File

@@ -8,8 +8,6 @@
#include "Engine/Graphics/Models/SkeletonData.h"
#include "Engine/Graphics/Models/SkinnedModelLOD.h"
class StreamSkinnedModelLODTask;
/// <summary>
/// Skinned model asset that contains model object made of meshes that can be rendered on the GPU using skeleton bones skinning.
/// </summary>
@@ -17,7 +15,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API SkinnedModel : public ModelBase
{
DECLARE_BINARY_ASSET_HEADER(SkinnedModel, 5);
friend SkinnedMesh;
friend StreamSkinnedModelLODTask;
public:
// Skeleton mapping descriptor.
struct FLAXENGINE_API SkeletonMapping
@@ -37,9 +34,6 @@ private:
Span<int32> NodesMapping;
};
bool _initialized = false;
int32 _loadedLODs = 0;
StreamSkinnedModelLODTask* _streamingTask = nullptr;
Dictionary<Asset*, SkeletonMappingData> _skeletonMappingCache;
public:
@@ -60,53 +54,11 @@ public:
~SkinnedModel();
public:
/// <summary>
/// Gets a value indicating whether this instance is initialized.
/// </summary>
FORCE_INLINE bool IsInitialized() const
{
return _initialized;
}
/// <summary>
/// Gets the amount of loaded model LODs.
/// </summary>
API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const
{
return _loadedLODs;
}
/// <summary>
/// Clamps the index of the LOD to be valid for rendering (only loaded LODs).
/// </summary>
/// <param name="index">The index.</param>
/// <returns>The resident LOD index.</returns>
FORCE_INLINE int32 ClampLODIndex(int32 index) const
{
return Math::Clamp(index, HighestResidentLODIndex(), LODs.Count() - 1);
}
/// <summary>
/// Gets index of the highest resident LOD (may be equal to LODs.Count if no LOD has been uploaded). Note: LOD=0 is the highest (top quality)
/// </summary>
FORCE_INLINE int32 HighestResidentLODIndex() const
{
return LODs.Count() - _loadedLODs;
}
/// <summary>
/// Determines whether any LOD has been initialized.
/// </summary>
bool HasAnyLODInitialized() const;
/// <summary>
/// Determines whether this model can be rendered.
/// </summary>
FORCE_INLINE bool CanBeRendered() const
{
return _loadedLODs > 0;
}
/// <summary>
/// Gets the skeleton nodes hierarchy.
/// </summary>
@@ -159,20 +111,6 @@ public:
API_PROPERTY() Array<String> GetBlendShapes();
public:
/// <summary>
/// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here).
/// </summary>
/// <param name="lodIndex">Index of the LOD.</param>
/// <returns>Task that will gather chunk data or null if already here.</returns>
ContentLoadTask* RequestLODDataAsync(int32 lodIndex);
/// <summary>
/// Gets the model LOD data (links bytes).
/// </summary>
/// <param name="lodIndex">Index of the LOD.</param>
/// <param name="data">The data (may be missing if failed to get it).</param>
void GetLODData(int32 lodIndex, BytesContainer& data) const;
/// <summary>
/// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup.
/// </summary>
@@ -322,24 +260,14 @@ public:
int32 GetLODsCount() const override;
void GetMeshes(Array<MeshBase*>& meshes, int32 lodIndex = 0) override;
void InitAsVirtual() override;
void CancelStreaming() override;
#if USE_EDITOR
void GetReferences(Array<Guid>& assets, Array<String>& files) const override;
#endif
// [StreamableResource]
int32 GetMaxResidency() const override;
int32 GetCurrentResidency() const override;
int32 GetAllocatedResidency() const override;
bool CanBeUpdated() const override;
Task* UpdateAllocation(int32 residency) override;
Task* CreateStreamingTask(int32 residency) override;
void CancelStreamingTasks() override;
protected:
// [ModelBase]
LoadResult load() override;
void unload(bool isReloading) override;
bool init(AssetInitData& initData) override;
AssetChunksFlag getChunksToPreload() const override;
};

View File

@@ -31,12 +31,12 @@ public:
}
template<typename IndexType>
void Init(uint32 vertices, uint32 triangles, Float3* positions, IndexType* indices)
void Init(uint32 vertices, uint32 triangles, const Float3* positions, const IndexType* indices)
{
Triangles.Clear();
Triangles.EnsureCapacity(triangles, false);
IndexType* it = indices;
const IndexType* it = indices;
for (uint32 i = 0; i < triangles; i++)
{
auto i0 = *(it++);

View File

@@ -136,26 +136,17 @@ namespace
bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0ElementType* vb0, const VB1ElementType* vb1, const VB2ElementType* vb2, const void* ib, bool use16BitIndices)
{
auto model = (Model*)_model;
Unload();
Release();
// Setup GPU resources
model->LODs[_lodIndex]._verticesCount -= _vertices;
const bool failed = Load(vertexCount, triangleCount, vb0, vb1, vb2, ib, use16BitIndices);
if (!failed)
{
model->LODs[_lodIndex]._verticesCount += _vertices;
// Calculate mesh bounds
BoundingBox bounds;
BoundingBox::FromPoints((const Float3*)vb0, vertexCount, bounds);
SetBounds(bounds);
// Send event (actors using this model can update bounds, etc.)
model->onLoaded();
}
return failed;
}
@@ -169,90 +160,19 @@ bool Mesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* ve
return ::UpdateMesh<uint32>(this, vertexCount, triangleCount, vertices, triangles, normals, tangents, uvs, colors);
}
void Mesh::Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs)
{
_model = model;
_lodIndex = lodIndex;
_index = index;
_materialSlotIndex = materialSlotIndex;
_use16BitIndexBuffer = false;
_hasLightmapUVs = hasLightmapUVs;
_box = box;
_sphere = sphere;
_vertices = 0;
_triangles = 0;
_vertexBuffers[0] = nullptr;
_vertexBuffers[1] = nullptr;
_vertexBuffers[2] = nullptr;
_indexBuffer = nullptr;
}
bool Mesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer)
{
// Cache data
uint32 indicesCount = triangles * 3;
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
GPUBuffer* vertexBuffer0 = nullptr;
GPUBuffer* vertexBuffer1 = nullptr;
GPUBuffer* vertexBuffer2 = nullptr;
GPUBuffer* indexBuffer = nullptr;
// Create GPU buffers
#if GPU_ENABLE_RESOURCE_NAMING
#define MESH_BUFFER_NAME(postfix) GetModel()->GetPath() + TEXT(postfix)
#else
#define MESH_BUFFER_NAME(postfix) String::Empty
#endif
vertexBuffer0 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB0"));
if (vertexBuffer0->Init(GPUBufferDescription::Vertex(VB0ElementType::GetLayout(), sizeof(VB0ElementType), vertices, vb0)))
goto ERROR_LOAD_END;
vertexBuffer1 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB1"));
if (vertexBuffer1->Init(GPUBufferDescription::Vertex(VB1ElementType::GetLayout(), sizeof(VB1ElementType), vertices, vb1)))
goto ERROR_LOAD_END;
Array<const void*, FixedAllocation<3>> vbData;
vbData.Add(vb0);
if (vb1)
vbData.Add(vb1);
if (vb2)
{
vertexBuffer2 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB2"));
if (vertexBuffer2->Init(GPUBufferDescription::Vertex(VB2ElementType::GetLayout(), sizeof(VB2ElementType), vertices, vb2)))
goto ERROR_LOAD_END;
}
indexBuffer = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".IB"));
if (indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib)))
goto ERROR_LOAD_END;
// Init collision proxy
#if USE_PRECISE_MESH_INTERSECTS
if (!_collisionProxy.HasData())
{
if (use16BitIndexBuffer)
_collisionProxy.Init<uint16>(vertices, triangles, (Float3*)vb0, (uint16*)ib);
else
_collisionProxy.Init<uint32>(vertices, triangles, (Float3*)vb0, (uint32*)ib);
}
#endif
// Initialize
_vertexBuffers[0] = vertexBuffer0;
_vertexBuffers[1] = vertexBuffer1;
_vertexBuffers[2] = vertexBuffer2;
_indexBuffer = indexBuffer;
_triangles = triangles;
_vertices = vertices;
_use16BitIndexBuffer = use16BitIndexBuffer;
_cachedVertexBuffers[0].Clear();
_cachedVertexBuffers[1].Clear();
_cachedVertexBuffers[2].Clear();
return false;
#undef MESH_BUFFER_NAME
ERROR_LOAD_END:
SAFE_DELETE_GPU_RESOURCE(vertexBuffer0);
SAFE_DELETE_GPU_RESOURCE(vertexBuffer1);
SAFE_DELETE_GPU_RESOURCE(vertexBuffer2);
SAFE_DELETE_GPU_RESOURCE(indexBuffer);
return true;
vbData.Add(vb2);
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
vbLayout.Add(VB0ElementType::GetLayout());
vbLayout.Add(VB1ElementType::GetLayout());
vbLayout.Add(VB2ElementType::GetLayout());
return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout);
}
void Mesh::Draw(const RenderContext& renderContext, MaterialBase* material, const Matrix& world, StaticFlags flags, bool receiveDecals, DrawPass drawModes, float perInstanceRandom, int8 sortOrder) const
@@ -424,6 +344,26 @@ void Mesh::Draw(const RenderContextBatch& renderContextBatch, const DrawInfo& in
renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, info.Flags, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals, info.SortOrder);
}
bool Mesh::Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout)
{
if (MeshBase::Init(vertices, triangles, vbData, ibData, use16BitIndexBuffer, vbLayout))
return true;
auto model = (Model*)_model;
if (model)
model->LODs[_lodIndex]._verticesCount += _vertices;
return false;
}
void Mesh::Release()
{
auto model = (Model*)_model;
if (model)
model->LODs[_lodIndex]._verticesCount -= _vertices;
MeshBase::Release();
}
bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const
{
if (_cachedVertexBuffers[0].IsEmpty())

View File

@@ -16,9 +16,6 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public MeshBase
{
DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, MeshBase);
protected:
bool _hasLightmapUVs;
public:
Mesh(const Mesh& other)
: Mesh()
@@ -40,11 +37,16 @@ public:
/// <summary>
/// Determines whether this mesh contains valid lightmap texture coordinates data.
/// </summary>
API_PROPERTY() FORCE_INLINE bool HasLightmapUVs() const
API_PROPERTY() bool HasLightmapUVs() const
{
return _hasLightmapUVs;
return LightmapUVsIndex != -1;
}
/// <summary>
/// Lightmap texture coordinates channel index.
/// </summary>
API_FIELD() int32 LightmapUVsIndex = -1;
public:
/// <summary>
/// Updates the model mesh (used by the virtual models created with Init rather than Load).
@@ -124,20 +126,9 @@ public:
bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, const Float3* vertices, const uint32* triangles, const Float3* normals = nullptr, const Float3* tangents = nullptr, const Float2* uvs = nullptr, const Color32* colors = nullptr);
public:
/// <summary>
/// Initializes instance of the <see cref="Mesh"/> class.
/// </summary>
/// <param name="model">The model.</param>
/// <param name="lodIndex">The LOD index.</param>
/// <param name="index">The mesh index.</param>
/// <param name="materialSlotIndex">The material slot index to use.</param>
/// <param name="box">The bounding box.</param>
/// <param name="sphere">The bounding sphere.</param>
/// <param name="hasLightmapUVs">The lightmap UVs flag.</param>
void Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs);
/// <summary>
/// Load mesh data and Initialize GPU buffers
/// [Deprecated in v1.10]
/// </summary>
/// <param name="vertices">Amount of vertices in the vertex buffer</param>
/// <param name="triangles">Amount of triangles in the index buffer</param>
@@ -147,7 +138,7 @@ public:
/// <param name="ib">Index buffer data</param>
/// <param name="use16BitIndexBuffer">True if use 16 bit indices for the index buffer (true: uint16, false: uint32).</param>
/// <returns>True if cannot load data, otherwise false.</returns>
bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer);
DEPRECATED("Use Init intead.") bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* vb1, const void* vb2, const void* ib, bool use16BitIndexBuffer);
public:
/// <summary>
@@ -181,6 +172,8 @@ public:
public:
// [MeshBase]
bool Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout) override;
void Release() override;
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
private:

View File

@@ -48,7 +48,6 @@ void MeshBase::SetMaterialSlotIndex(int32 value)
LOG(Warning, "Cannot set mesh material slot to {0} while model has {1} slots.", value, _model->MaterialSlots.Count());
return;
}
_materialSlotIndex = value;
}
@@ -56,9 +55,94 @@ void MeshBase::SetBounds(const BoundingBox& box)
{
_box = box;
BoundingSphere::FromBox(box, _sphere);
_hasBounds = true;
}
void MeshBase::Unload()
void MeshBase::SetBounds(const BoundingBox& box, const BoundingSphere& sphere)
{
_box = box;
_sphere = sphere;
_hasBounds = true;
if (_model && _model->IsLoaded())
{
// Send event (actors using this model can update bounds, etc.)
_model->onLoaded();
}
}
bool MeshBase::Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout)
{
CHECK_RETURN(vbData.HasItems() && vertices, true);
CHECK_RETURN(ibData, true);
CHECK_RETURN(vbLayout.Count() >= vbData.Count(), true);
ASSERT(_model);
GPUBuffer* vertexBuffer0 = nullptr;
GPUBuffer* vertexBuffer1 = nullptr;
GPUBuffer* vertexBuffer2 = nullptr;
GPUBuffer* indexBuffer = nullptr;
// Create GPU buffers
#if GPU_ENABLE_RESOURCE_NAMING
const String& modelPath = _model->GetPath();
#define MESH_BUFFER_NAME(postfix) modelPath + TEXT(postfix)
#else
#define MESH_BUFFER_NAME(postfix) String::Empty
#endif
vertexBuffer0 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB0"));
if (vertexBuffer0->Init(GPUBufferDescription::Vertex(vbLayout[0], vertices, vbData[0])))
goto ERROR_LOAD_END;
if (vbData.Count() >= 2 && vbData[1])
{
vertexBuffer1 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB1"));
if (vertexBuffer1->Init(GPUBufferDescription::Vertex(vbLayout[1], vertices, vbData[1])))
goto ERROR_LOAD_END;
}
if (vbData.Count() >= 3 && vbData[2])
{
vertexBuffer2 = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".VB2"));
if (vertexBuffer2->Init(GPUBufferDescription::Vertex(vbLayout[2], vertices, vbData[2])))
goto ERROR_LOAD_END;
}
indexBuffer = GPUDevice::Instance->CreateBuffer(MESH_BUFFER_NAME(".IB"));
if (indexBuffer->Init(GPUBufferDescription::Index(use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32), triangles * 3, ibData)))
goto ERROR_LOAD_END;
// Init collision proxy
#if USE_PRECISE_MESH_INTERSECTS
if (!_collisionProxy.HasData())
{
if (use16BitIndexBuffer)
_collisionProxy.Init<uint16>(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData);
else
_collisionProxy.Init<uint32>(vertices, triangles, (const Float3*)vbData[0], (const uint32*)ibData);
}
#endif
// Initialize
_vertexBuffers[0] = vertexBuffer0;
_vertexBuffers[1] = vertexBuffer1;
_vertexBuffers[2] = vertexBuffer2;
_indexBuffer = indexBuffer;
_triangles = triangles;
_vertices = vertices;
_use16BitIndexBuffer = use16BitIndexBuffer;
_cachedVertexBuffers[0].Clear();
_cachedVertexBuffers[1].Clear();
_cachedVertexBuffers[2].Clear();
return false;
#undef MESH_BUFFER_NAME
ERROR_LOAD_END:
SAFE_DELETE_GPU_RESOURCE(vertexBuffer0);
SAFE_DELETE_GPU_RESOURCE(vertexBuffer1);
SAFE_DELETE_GPU_RESOURCE(vertexBuffer2);
SAFE_DELETE_GPU_RESOURCE(indexBuffer);
return true;
}
void MeshBase::Release()
{
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[0]);
SAFE_DELETE_GPU_RESOURCE(_vertexBuffers[1]);

View File

@@ -31,30 +31,40 @@ class BlendShapesInstance;
API_CLASS(Abstract, NoSpawn) class FLAXENGINE_API MeshBase : public ScriptingObject
{
DECLARE_SCRIPTING_TYPE_MINIMAL(MeshBase);
friend class Model;
friend class SkinnedModel;
protected:
ModelBase* _model;
BoundingBox _box;
BoundingSphere _sphere;
ModelBase* _model = nullptr;
BoundingBox _box = BoundingBox::Zero;
BoundingSphere _sphere = BoundingSphere::Empty;
int32 _index;
int32 _lodIndex;
uint32 _vertices;
uint32 _triangles;
int32 _materialSlotIndex;
bool _use16BitIndexBuffer;
int32 _index = 0;
int32 _lodIndex = 0;
uint32 _vertices = 0;
uint32 _triangles = 0;
int32 _materialSlotIndex = 0;
bool _use16BitIndexBuffer = false;
bool _hasBounds = false;
GPUBuffer* _vertexBuffers[3] = {};
GPUBuffer* _indexBuffer = nullptr;
mutable Array<byte> _cachedVertexBuffers[3];
mutable Array<byte> _cachedIndexBuffer;
mutable int32 _cachedIndexBufferCount;
mutable int32 _cachedIndexBufferCount = 0;
#if USE_PRECISE_MESH_INTERSECTS
CollisionProxy _collisionProxy;
#endif
void Link(ModelBase* model, int32 lodIndex, int32 index)
{
_model = model;
_lodIndex = lodIndex;
_index = index;
}
explicit MeshBase(const SpawnParams& params)
: ScriptingObject(params)
{
@@ -169,6 +179,13 @@ public:
/// <param name="box">The bounding box.</param>
void SetBounds(const BoundingBox& box);
/// <summary>
/// Sets the mesh bounds.
/// </summary>
/// <param name="box">The bounding box.</param>
/// <param name="sphere">The bounding sphere.</param>
void SetBounds(const BoundingBox& box, const BoundingSphere& sphere);
/// <summary>
/// Gets the index buffer.
/// </summary>
@@ -189,9 +206,30 @@ public:
public:
/// <summary>
/// Unloads the mesh data (vertex buffers and cache). The opposite to Load.
/// Initializes the mesh buffers.
/// </summary>
void Unload();
/// <param name="vertices">Amount of vertices in the vertex buffer.</param>
/// <param name="triangles">Amount of triangles in the index buffer.</param>
/// <param name="vbData">Array with pointers to vertex buffers initial data (layout defined by <paramref name="vertexLayout"/>).</param>
/// <param name="ibData">Pointer to index buffer data. Data is uint16 or uint32 depending on <paramref name="use16BitIndexBuffer"/> value.</param>
/// <param name="use16BitIndexBuffer">True to use 16-bit indices for the index buffer (true: uint16, false: uint32).</param>
/// <param name="vbLayout">Layout descriptors for the vertex buffers attributes (one for each vertex buffer).</param>
/// <returns>True if failed, otherwise false.</returns>
virtual bool Init(uint32 vertices, uint32 triangles, const Array<const void*, FixedAllocation<3>>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array<GPUVertexLayout*, FixedAllocation<3>>& vbLayout);
/// <summary>
/// Releases the mesh data (GPU buffers and local cache).
/// </summary>
virtual void Release();
/// <summary>
/// Unloads the mesh data (vertex buffers and cache). The opposite to Load.
/// [Deprecated in v1.10]
/// </summary>
DEPRECATED("Use Release instead.") void Unload()
{
Release();
}
public:
/// <summary>

View File

@@ -1,161 +0,0 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "ModelLOD.h"
#include "MeshDeformation.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Serialization/MemoryReadStream.h"
bool ModelLOD::Load(MemoryReadStream& stream)
{
// Load LOD for each mesh
_verticesCount = 0;
for (int32 i = 0; i < Meshes.Count(); i++)
{
// #MODEL_DATA_FORMAT_USAGE
uint32 vertices;
stream.ReadUint32(&vertices);
_verticesCount += 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 (Meshes[i].Load(vertices, triangles, vb0, vb1, vb2, ib, use16BitIndexBuffer))
{
LOG(Warning, "Cannot initialize mesh {0}. Vertices: {1}, triangles: {2}", i, vertices, triangles);
return true;
}
}
return false;
}
void ModelLOD::Unload()
{
// Unload LOD for each mesh
for (int32 i = 0; i < Meshes.Count(); i++)
{
Meshes[i].Unload();
}
}
void ModelLOD::Dispose()
{
_model = nullptr;
ScreenSize = 0.0f;
Meshes.Resize(0);
}
bool ModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, Mesh** mesh)
{
bool result = false;
Real closest = MAX_Real;
Vector3 closestNormal = Vector3::Up;
for (int32 i = 0; i < Meshes.Count(); i++)
{
Real dst;
Vector3 nrm;
if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest)
{
result = true;
*mesh = &Meshes[i];
closest = dst;
closestNormal = nrm;
}
}
distance = closest;
normal = closestNormal;
return result;
}
bool ModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, Mesh** mesh)
{
bool result = false;
Real closest = MAX_Real;
Vector3 closestNormal = Vector3::Up;
for (int32 i = 0; i < Meshes.Count(); i++)
{
Real dst;
Vector3 nrm;
if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest)
{
result = true;
*mesh = &Meshes[i];
closest = dst;
closestNormal = nrm;
}
}
distance = closest;
normal = closestNormal;
return result;
}
BoundingBox ModelLOD::GetBox(const Matrix& world) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[meshIndex];
mesh.GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
Vector3::Transform(corners[i], world, tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
BoundingBox ModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[meshIndex];
BoundingBox box = mesh.GetBox();
if (deformation)
deformation->GetBounds(_lodIndex, meshIndex, box);
box.GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
transform.LocalToWorld(corners[i], tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
BoundingBox ModelLOD::GetBox() const
{
Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
Meshes[meshIndex].GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
min = Vector3::Min(min, corners[i]);
max = Vector3::Max(max, corners[i]);
}
}
return BoundingBox(min, max);
}

View File

@@ -2,7 +2,6 @@
#pragma once
#include "Engine/Core/Collections/Array.h"
#include "Mesh.h"
class MemoryReadStream;
@@ -18,7 +17,14 @@ API_CLASS(NoSpawn) class FLAXENGINE_API ModelLOD : public ScriptingObject
private:
Model* _model = nullptr;
int32 _lodIndex = 0;
uint32 _verticesCount;
uint32 _verticesCount = 0;
void Link(Model* model, int32 lodIndex)
{
_model = model;
_lodIndex = lodIndex;
_verticesCount = 0;
}
public:
/// <summary>
@@ -57,24 +63,6 @@ public:
return _verticesCount;
}
public:
/// <summary>
/// Initializes the LOD from the data stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <returns>True if fails, otherwise false.</returns>
bool Load(MemoryReadStream& stream);
/// <summary>
/// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load.
/// </summary>
void Unload();
/// <summary>
/// Cleanups the data.
/// </summary>
void Dispose();
public:
/// <summary>
/// Determines if there is an intersection between the Model and a Ray in given world using given instance

View File

@@ -94,71 +94,17 @@ void SkeletonData::Dispose()
Bones.Resize(0);
}
void SkinnedMesh::Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere)
{
_model = model;
_index = index;
_lodIndex = lodIndex;
_materialSlotIndex = materialSlotIndex;
_use16BitIndexBuffer = false;
_box = box;
_sphere = sphere;
_vertices = 0;
_triangles = 0;
_vertexBuffers[0] = nullptr;
_indexBuffer = nullptr;
_cachedIndexBuffer.Clear();
_cachedVertexBuffers[0].Clear();
BlendShapes.Clear();
}
bool SkinnedMesh::Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer)
{
// Cache data
uint32 indicesCount = triangles * 3;
uint32 ibStride = use16BitIndexBuffer ? sizeof(uint16) : sizeof(uint32);
GPUBuffer* vertexBuffer = nullptr;
GPUBuffer* indexBuffer = nullptr;
// Create vertex buffer
#if GPU_ENABLE_RESOURCE_NAMING
vertexBuffer = GPUDevice::Instance->CreateBuffer(GetSkinnedModel()->GetPath() + TEXT(".VB"));
#else
vertexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty);
#endif
if (vertexBuffer->Init(GPUBufferDescription::Vertex(VB0SkinnedElementType::GetLayout(), sizeof(VB0SkinnedElementType), vertices, vb0)))
goto ERROR_LOAD_END;
// Create index buffer
#if GPU_ENABLE_RESOURCE_NAMING
indexBuffer = GPUDevice::Instance->CreateBuffer(GetSkinnedModel()->GetPath() + TEXT(".IB"));
#else
indexBuffer = GPUDevice::Instance->CreateBuffer(String::Empty);
#endif
if (indexBuffer->Init(GPUBufferDescription::Index(ibStride, indicesCount, ib)))
goto ERROR_LOAD_END;
// Initialize
_vertexBuffers[0] = vertexBuffer;
_indexBuffer = indexBuffer;
_triangles = triangles;
_vertices = vertices;
_use16BitIndexBuffer = use16BitIndexBuffer;
return false;
ERROR_LOAD_END:
SAFE_DELETE_GPU_RESOURCE(vertexBuffer);
SAFE_DELETE_GPU_RESOURCE(indexBuffer);
return true;
Array<const void*, FixedAllocation<3>> vbData;
vbData.Add(vb0);
Array<GPUVertexLayout*, FixedAllocation<3>> vbLayout;
vbLayout.Add(VB0SkinnedElementType::GetLayout());
return Init(vertices, triangles, vbData, ib, use16BitIndexBuffer, vbLayout);
}
bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0SkinnedElementType* vb, const void* ib, bool use16BitIndices)
{
auto model = (SkinnedModel*)_model;
// Setup GPU resources
const bool failed = Load(vertexCount, triangleCount, vb, ib, use16BitIndices);
if (!failed)
@@ -167,11 +113,7 @@ bool SkinnedMesh::UpdateMesh(uint32 vertexCount, uint32 triangleCount, const VB0
BoundingBox bounds;
BoundingBox::FromPoints((const Float3*)vb, vertexCount, bounds);
SetBounds(bounds);
// Send event (actors using this model can update bounds, etc.)
model->onLoaded();
}
return failed;
}
@@ -267,6 +209,13 @@ void SkinnedMesh::Draw(const RenderContextBatch& renderContextBatch, const DrawI
renderContextBatch.GetMainContext().List->AddDrawCall(renderContextBatch, drawModes, StaticFlags::None, shadowsMode, info.Bounds, drawCall, entry.ReceiveDecals, info.SortOrder);
}
void SkinnedMesh::Release()
{
MeshBase::Release();
BlendShapes.Clear();
}
bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const
{
if (_cachedVertexBuffers[0].IsEmpty())

View File

@@ -36,19 +36,9 @@ public:
Array<BlendShape> BlendShapes;
public:
/// <summary>
/// Initializes a new instance of the <see cref="SkinnedMesh"/> class.
/// </summary>
/// <param name="model">The model.</param>
/// <param name="lodIndex">The model LOD index.</param>
/// <param name="index">The mesh index.</param>
/// <param name="materialSlotIndex">The material slot index to use.</param>
/// <param name="box">The bounding box.</param>
/// <param name="sphere">The bounding sphere.</param>
void Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere);
/// <summary>
/// Load mesh data and Initialize GPU buffers
/// [Deprecated in v1.10]
/// </summary>
/// <param name="vertices">Amount of vertices in the vertex buffer</param>
/// <param name="triangles">Amount of triangles in the index buffer</param>
@@ -56,7 +46,7 @@ public:
/// <param name="ib">Index buffer data</param>
/// <param name="use16BitIndexBuffer">True if use 16 bit indices for the index buffer (true: uint16, false: uint32).</param>
/// <returns>True if cannot load data, otherwise false.</returns>
bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer);
DEPRECATED("Use Init intead.") bool Load(uint32 vertices, uint32 triangles, const void* vb0, const void* ib, bool use16BitIndexBuffer);
public:
/// <summary>
@@ -128,6 +118,7 @@ public:
public:
// [MeshBase]
void Release() override;
bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override;
private:

View File

@@ -1,200 +0,0 @@
// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved.
#include "SkinnedModelLOD.h"
#include "MeshDeformation.h"
#include "Engine/Core/Log.h"
#include "Engine/Core/Math/Transform.h"
#include "Engine/Graphics/GPUDevice.h"
#include "Engine/Content/Assets/Model.h"
#include "Engine/Serialization/MemoryReadStream.h"
bool SkinnedModelLOD::Load(MemoryReadStream& stream)
{
// Load LOD for each mesh
byte version = stream.ReadByte();
for (int32 i = 0; i < Meshes.Count(); i++)
{
auto& mesh = 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 {0}. Incorect blend shapes amount: {1} (expected: {2})", i, 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 {0}. Vertices: {1}, triangles: {2}", i, vertices, triangles);
return true;
}
}
return false;
}
void SkinnedModelLOD::Unload()
{
// Unload LOD for each mesh
for (int32 i = 0; i < Meshes.Count(); i++)
{
Meshes[i].Unload();
}
}
void SkinnedModelLOD::Dispose()
{
_model = nullptr;
ScreenSize = 0.0f;
Meshes.Resize(0);
}
bool SkinnedModelLOD::Intersects(const Ray& ray, const Matrix& world, Real& distance, Vector3& normal, SkinnedMesh** mesh)
{
// Check all meshes
bool result = false;
Real closest = MAX_float;
Vector3 closestNormal = Vector3::Up;
for (int32 i = 0; i < Meshes.Count(); i++)
{
// Test intersection with mesh and check if is closer than previous
Real dst;
Vector3 nrm;
if (Meshes[i].Intersects(ray, world, dst, nrm) && dst < closest)
{
result = true;
*mesh = &Meshes[i];
closest = dst;
closestNormal = nrm;
}
}
distance = closest;
normal = closestNormal;
return result;
}
bool SkinnedModelLOD::Intersects(const Ray& ray, const Transform& transform, Real& distance, Vector3& normal, SkinnedMesh** mesh)
{
// Check all meshes
bool result = false;
Real closest = MAX_float;
Vector3 closestNormal = Vector3::Up;
for (int32 i = 0; i < Meshes.Count(); i++)
{
// Test intersection with mesh and check if is closer than previous
Real dst;
Vector3 nrm;
if (Meshes[i].Intersects(ray, transform, dst, nrm) && dst < closest)
{
result = true;
*mesh = &Meshes[i];
closest = dst;
closestNormal = nrm;
}
}
distance = closest;
normal = closestNormal;
return result;
}
BoundingBox SkinnedModelLOD::GetBox(const Matrix& world) const
{
// Find minimum and maximum points of all the meshes
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 j = 0; j < Meshes.Count(); j++)
{
const auto& mesh = Meshes[j];
mesh.GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
Vector3::Transform(corners[i], world, tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
BoundingBox SkinnedModelLOD::GetBox(const Transform& transform, const MeshDeformation* deformation) const
{
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 meshIndex = 0; meshIndex < Meshes.Count(); meshIndex++)
{
const auto& mesh = Meshes[meshIndex];
BoundingBox box = mesh.GetBox();
if (deformation)
deformation->GetBounds(_lodIndex, meshIndex, box);
box.GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
transform.LocalToWorld(corners[i], tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
}
return BoundingBox(min, max);
}
BoundingBox SkinnedModelLOD::GetBox(const Matrix& world, int32 meshIndex) const
{
// Find minimum and maximum points of the mesh
Vector3 tmp, min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
const auto& mesh = Meshes[meshIndex];
mesh.GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
Vector3::Transform(corners[i], world, tmp);
min = Vector3::Min(min, tmp);
max = Vector3::Max(max, tmp);
}
return BoundingBox(min, max);
}
BoundingBox SkinnedModelLOD::GetBox() const
{
// Find minimum and maximum points of the mesh in given world
Vector3 min = Vector3::Maximum, max = Vector3::Minimum;
Vector3 corners[8];
for (int32 j = 0; j < Meshes.Count(); j++)
{
Meshes[j].GetBox().GetCorners(corners);
for (int32 i = 0; i < 8; i++)
{
min = Vector3::Min(min, corners[i]);
max = Vector3::Max(max, corners[i]);
}
}
return BoundingBox(min, max);
}

View File

@@ -2,7 +2,6 @@
#pragma once
#include "Engine/Core/Collections/Array.h"
#include "SkinnedMesh.h"
class MemoryReadStream;
@@ -46,23 +45,6 @@ public:
return Meshes.HasItems() && Meshes.Last().IsInitialized();
}
public:
/// <summary>
/// Initializes the LOD from the data stream.
/// </summary>
/// <param name="stream">The stream.</param>
/// <returns>True if fails, otherwise false.</returns>
bool Load(MemoryReadStream& stream);
/// <summary>
/// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load.
/// </summary>
void Unload();
/// <summary>
/// Cleanups the data.
/// </summary>
void Dispose();
public:
/// <summary>

View File

@@ -87,8 +87,8 @@ void GPUVertexLayout::SetElements(const Elements& elements, uint32 offsets[GPU_M
strides[e.Slot] = Math::Max(strides[e.Slot], offsets[e.Slot]);
}
_stride = 0;
for (int32 i = 0; i < GPU_MAX_VB_BINDED; i++)
_stride += strides[i];
for (uint32 stride : strides)
_stride += stride;
}
GPUVertexLayout* GPUVertexLayout::Get(const Elements& elements)