diff --git a/Source/Engine/Content/Assets/Model.cpp b/Source/Engine/Content/Assets/Model.cpp index 37e0d1cc8..8ba26658c 100644 --- a/Source/Engine/Content/Assets/Model.cpp +++ b/Source/Engine/Content/Assets/Model.cpp @@ -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"); -/// -/// Model LOD streaming task. -/// -class StreamModelLODTask : public STREAM_TASK_BASE -{ -private: - WeakAssetReference _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 = _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& 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& 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& assets, Array& 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(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); +} diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index dd52ba34a..3f78f973d 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -6,7 +6,6 @@ #include "Engine/Graphics/Models/ModelLOD.h" class Mesh; -class StreamModelLODTask; /// /// 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: /// @@ -38,40 +33,6 @@ public: ~Model(); public: - /// - /// Gets a value indicating whether this instance is initialized. - /// - FORCE_INLINE bool IsInitialized() const - { - return LODs.HasItems(); - } - - /// - /// Gets the amount of loaded model LODs. - /// - API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const - { - return _loadedLODs; - } - - /// - /// Clamps the index of the LOD to be valid for rendering (only loaded LODs). - /// - /// The index. - /// The resident LOD index. - FORCE_INLINE int32 ClampLODIndex(int32 index) const - { - return Math::Clamp(index, HighestResidentLODIndex(), LODs.Count() - 1); - } - - /// - /// 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) - /// - FORCE_INLINE int32 HighestResidentLODIndex() const - { - return LODs.Count() - _loadedLODs; - } - /// /// Determines whether any LOD has been initialized. /// @@ -80,37 +41,6 @@ public: return LODs.HasItems() && LODs.Last().HasAnyMeshInitialized(); } - /// - /// Determines whether this model can be rendered. - /// - FORCE_INLINE bool CanBeRendered() const - { - return _loadedLODs > 0; - } - -public: - /// - /// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here). - /// - /// Index of the LOD. - /// Task that will gather chunk data or null if already here. - ContentLoadTask* RequestLODDataAsync(int32 lodIndex) - { - const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); - return RequestChunkDataAsync(chunkIndex); - } - - /// - /// Gets the model LOD data (links bytes). - /// - /// Index of the LOD. - /// The data (may be missing if failed to get it). - void GetLODData(int32 lodIndex, BytesContainer& data) const - { - const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); - GetChunkData(chunkIndex, data); - } - public: /// /// 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& meshes, int32 lodIndex = 0) override; void InitAsVirtual() override; - void CancelStreaming() override; -#if USE_EDITOR - void GetReferences(Array& assets, Array& 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; }; diff --git a/Source/Engine/Content/Assets/ModelBase.cpp b/Source/Engine/Content/Assets/ModelBase.cpp new file mode 100644 index 000000000..6e7fc3457 --- /dev/null +++ b/Source/Engine/Content/Assets/ModelBase.cpp @@ -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 + +/// +/// Model LOD streaming task. +/// +class StreamModelLODTask : public STREAM_TASK_BASE +{ +private: + WeakAssetReference _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 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 meshes; + model->GetMeshes(meshes, _lodIndex); + if (model->Is()) + { + 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(vertices); + const auto ib = stream.Move(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(vertices); + auto vb1 = stream.Move(vertices); + bool hasColors = stream.ReadBool(); + VB2ElementType18* vb2 = nullptr; + if (hasColors) + { + vb2 = stream.Move(vertices); + } + auto ib = stream.Move(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& assets, Array& 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(this, lodIndex); + if (result) + result->ContinueWith(_streamingTask); + else + result = _streamingTask; + } + else + { + // Do the quick data release + Array 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; +} diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index a0d0091a5..ab683c116 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -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: /// /// The Sign Distant Field (SDF) data for the model. @@ -106,6 +115,48 @@ public: return MaterialSlots.Count(); } + /// + /// Gets a value indicating whether this instance is initialized. + /// + FORCE_INLINE bool IsInitialized() const + { + return _initialized; + } + + /// + /// Clamps the index of the LOD to be valid for rendering (only loaded LODs). + /// + /// The index. + /// The resident LOD index. + FORCE_INLINE int32 ClampLODIndex(int32 index) const + { + return Math::Clamp(index, HighestResidentLODIndex(), GetLODsCount() - 1); + } + + /// + /// 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) + /// + FORCE_INLINE int32 HighestResidentLODIndex() const + { + return GetLODsCount() - _loadedLODs; + } + + /// + /// Gets the amount of loaded model LODs. + /// + API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const + { + return _loadedLODs; + } + + /// + /// Determines whether this model can be rendered. + /// + FORCE_INLINE bool CanBeRendered() const + { + return _loadedLODs > 0; + } + /// /// Resizes the material slots collection. Updates meshes that were using removed slots. /// @@ -127,4 +178,37 @@ public: /// Gets the meshes for a particular LOD index. /// virtual void GetMeshes(Array& meshes, int32 lodIndex = 0) = 0; + +public: + /// + /// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here). + /// + /// Index of the LOD. + /// Task that will gather chunk data or null if already here. + ContentLoadTask* RequestLODDataAsync(int32 lodIndex); + + /// + /// Gets the model LOD data (links bytes). + /// + /// Index of the LOD. + /// The data (it may be missing if failed to get it). + void GetLODData(int32 lodIndex, BytesContainer& data) const; + +public: + // [BinaryAsset] + void CancelStreaming() override; +#if USE_EDITOR + void GetReferences(Array& assets, Array& 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; }; diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 80131b03b..0b9c69275 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -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); \ - } - -/// -/// Skinned model data streaming task -/// -class StreamSkinnedModelLODTask : public ThreadPoolTask -{ -private: - WeakAssetReference _asset; - int32 _lodIndex; - FlaxStorage::LockData _dataLock; - -public: - /// - /// Init - /// - /// Parent model - /// LOD to stream index - 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 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 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& 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& 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& assets, Array& 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(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); +} diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h index cf778027d..fc64e9339 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.h +++ b/Source/Engine/Content/Assets/SkinnedModel.h @@ -8,8 +8,6 @@ #include "Engine/Graphics/Models/SkeletonData.h" #include "Engine/Graphics/Models/SkinnedModelLOD.h" -class StreamSkinnedModelLODTask; - /// /// Skinned model asset that contains model object made of meshes that can be rendered on the GPU using skeleton bones skinning. /// @@ -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 NodesMapping; }; - bool _initialized = false; - int32 _loadedLODs = 0; - StreamSkinnedModelLODTask* _streamingTask = nullptr; Dictionary _skeletonMappingCache; public: @@ -60,53 +54,11 @@ public: ~SkinnedModel(); public: - /// - /// Gets a value indicating whether this instance is initialized. - /// - FORCE_INLINE bool IsInitialized() const - { - return _initialized; - } - - /// - /// Gets the amount of loaded model LODs. - /// - API_PROPERTY() FORCE_INLINE int32 GetLoadedLODs() const - { - return _loadedLODs; - } - - /// - /// Clamps the index of the LOD to be valid for rendering (only loaded LODs). - /// - /// The index. - /// The resident LOD index. - FORCE_INLINE int32 ClampLODIndex(int32 index) const - { - return Math::Clamp(index, HighestResidentLODIndex(), LODs.Count() - 1); - } - - /// - /// 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) - /// - FORCE_INLINE int32 HighestResidentLODIndex() const - { - return LODs.Count() - _loadedLODs; - } - /// /// Determines whether any LOD has been initialized. /// bool HasAnyLODInitialized() const; - /// - /// Determines whether this model can be rendered. - /// - FORCE_INLINE bool CanBeRendered() const - { - return _loadedLODs > 0; - } - /// /// Gets the skeleton nodes hierarchy. /// @@ -159,20 +111,6 @@ public: API_PROPERTY() Array GetBlendShapes(); public: - /// - /// Requests the LOD data asynchronously (creates task that will gather chunk data or null if already here). - /// - /// Index of the LOD. - /// Task that will gather chunk data or null if already here. - ContentLoadTask* RequestLODDataAsync(int32 lodIndex); - - /// - /// Gets the model LOD data (links bytes). - /// - /// Index of the LOD. - /// The data (may be missing if failed to get it). - void GetLODData(int32 lodIndex, BytesContainer& data) const; - /// /// Gets the skeleton mapping for a given asset (animation or other skinned model). Uses identity mapping or manually created retargeting setup. /// @@ -322,24 +260,14 @@ public: int32 GetLODsCount() const override; void GetMeshes(Array& meshes, int32 lodIndex = 0) override; void InitAsVirtual() override; - void CancelStreaming() override; -#if USE_EDITOR - void GetReferences(Array& assets, Array& 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; }; diff --git a/Source/Engine/Graphics/Models/CollisionProxy.h b/Source/Engine/Graphics/Models/CollisionProxy.h index af3873d58..00a6a6d51 100644 --- a/Source/Engine/Graphics/Models/CollisionProxy.h +++ b/Source/Engine/Graphics/Models/CollisionProxy.h @@ -31,12 +31,12 @@ public: } template - 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++); diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index f948ab51d..c6dcf8979 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -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(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> 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(vertices, triangles, (Float3*)vb0, (uint16*)ib); - else - _collisionProxy.Init(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> 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>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& 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()) diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index 425bfbec7..8dba159a2 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -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: /// /// Determines whether this mesh contains valid lightmap texture coordinates data. /// - API_PROPERTY() FORCE_INLINE bool HasLightmapUVs() const + API_PROPERTY() bool HasLightmapUVs() const { - return _hasLightmapUVs; + return LightmapUVsIndex != -1; } + /// + /// Lightmap texture coordinates channel index. + /// + API_FIELD() int32 LightmapUVsIndex = -1; + public: /// /// 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: - /// - /// Initializes instance of the class. - /// - /// The model. - /// The LOD index. - /// The mesh index. - /// The material slot index to use. - /// The bounding box. - /// The bounding sphere. - /// The lightmap UVs flag. - void Init(Model* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere, bool hasLightmapUVs); - /// /// Load mesh data and Initialize GPU buffers + /// [Deprecated in v1.10] /// /// Amount of vertices in the vertex buffer /// Amount of triangles in the index buffer @@ -147,7 +138,7 @@ public: /// Index buffer data /// True if use 16 bit indices for the index buffer (true: uint16, false: uint32). /// True if cannot load data, otherwise false. - 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: /// @@ -181,6 +172,8 @@ public: public: // [MeshBase] + bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout) override; + void Release() override; bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override; private: diff --git a/Source/Engine/Graphics/Models/MeshBase.cpp b/Source/Engine/Graphics/Models/MeshBase.cpp index c95b58acf..f58527337 100644 --- a/Source/Engine/Graphics/Models/MeshBase.cpp +++ b/Source/Engine/Graphics/Models/MeshBase.cpp @@ -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>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& 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(vertices, triangles, (const Float3*)vbData[0], (const uint16*)ibData); + else + _collisionProxy.Init(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]); diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index 309fea9e5..4dc861e6a 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -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 _cachedVertexBuffers[3]; mutable Array _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: /// The bounding box. void SetBounds(const BoundingBox& box); + /// + /// Sets the mesh bounds. + /// + /// The bounding box. + /// The bounding sphere. + void SetBounds(const BoundingBox& box, const BoundingSphere& sphere); + /// /// Gets the index buffer. /// @@ -189,9 +206,30 @@ public: public: /// - /// Unloads the mesh data (vertex buffers and cache). The opposite to Load. + /// Initializes the mesh buffers. /// - void Unload(); + /// Amount of vertices in the vertex buffer. + /// Amount of triangles in the index buffer. + /// Array with pointers to vertex buffers initial data (layout defined by ). + /// Pointer to index buffer data. Data is uint16 or uint32 depending on value. + /// True to use 16-bit indices for the index buffer (true: uint16, false: uint32). + /// Layout descriptors for the vertex buffers attributes (one for each vertex buffer). + /// True if failed, otherwise false. + virtual bool Init(uint32 vertices, uint32 triangles, const Array>& vbData, const void* ibData, bool use16BitIndexBuffer, const Array>& vbLayout); + + /// + /// Releases the mesh data (GPU buffers and local cache). + /// + virtual void Release(); + + /// + /// Unloads the mesh data (vertex buffers and cache). The opposite to Load. + /// [Deprecated in v1.10] + /// + DEPRECATED("Use Release instead.") void Unload() + { + Release(); + } public: /// diff --git a/Source/Engine/Graphics/Models/ModelLOD.cpp b/Source/Engine/Graphics/Models/ModelLOD.cpp deleted file mode 100644 index 095c14f89..000000000 --- a/Source/Engine/Graphics/Models/ModelLOD.cpp +++ /dev/null @@ -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(vertices); - auto vb1 = stream.Move(vertices); - bool hasColors = stream.ReadBool(); - VB2ElementType18* vb2 = nullptr; - if (hasColors) - { - vb2 = stream.Move(vertices); - } - auto ib = stream.Move(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); -} diff --git a/Source/Engine/Graphics/Models/ModelLOD.h b/Source/Engine/Graphics/Models/ModelLOD.h index a6937739e..4934e6cc2 100644 --- a/Source/Engine/Graphics/Models/ModelLOD.h +++ b/Source/Engine/Graphics/Models/ModelLOD.h @@ -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: /// @@ -57,24 +63,6 @@ public: return _verticesCount; } -public: - /// - /// Initializes the LOD from the data stream. - /// - /// The stream. - /// True if fails, otherwise false. - bool Load(MemoryReadStream& stream); - - /// - /// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load. - /// - void Unload(); - - /// - /// Cleanups the data. - /// - void Dispose(); - public: /// /// Determines if there is an intersection between the Model and a Ray in given world using given instance diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index b7e2ddc6a..5473b549a 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -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> vbData; + vbData.Add(vb0); + Array> 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()) diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index b8dd38696..f28ea7fcd 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -36,19 +36,9 @@ public: Array BlendShapes; public: - /// - /// Initializes a new instance of the class. - /// - /// The model. - /// The model LOD index. - /// The mesh index. - /// The material slot index to use. - /// The bounding box. - /// The bounding sphere. - void Init(SkinnedModel* model, int32 lodIndex, int32 index, int32 materialSlotIndex, const BoundingBox& box, const BoundingSphere& sphere); - /// /// Load mesh data and Initialize GPU buffers + /// [Deprecated in v1.10] /// /// Amount of vertices in the vertex buffer /// Amount of triangles in the index buffer @@ -56,7 +46,7 @@ public: /// Index buffer data /// True if use 16 bit indices for the index buffer (true: uint16, false: uint32). /// True if cannot load data, otherwise false. - 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: /// @@ -128,6 +118,7 @@ public: public: // [MeshBase] + void Release() override; bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override; private: diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp b/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp deleted file mode 100644 index e16a78291..000000000 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.cpp +++ /dev/null @@ -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(vertices); - const auto ib = stream.Move(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); -} diff --git a/Source/Engine/Graphics/Models/SkinnedModelLOD.h b/Source/Engine/Graphics/Models/SkinnedModelLOD.h index 66cf77759..f7c78f3d1 100644 --- a/Source/Engine/Graphics/Models/SkinnedModelLOD.h +++ b/Source/Engine/Graphics/Models/SkinnedModelLOD.h @@ -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: - /// - /// Initializes the LOD from the data stream. - /// - /// The stream. - /// True if fails, otherwise false. - bool Load(MemoryReadStream& stream); - - /// - /// Unloads the LOD meshes data (vertex buffers and cache). It won't dispose the meshes collection. The opposite to Load. - /// - void Unload(); - - /// - /// Cleanups the data. - /// - void Dispose(); public: /// diff --git a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp index 880dc1f00..659bcb344 100644 --- a/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp +++ b/Source/Engine/Graphics/Shaders/GPUVertexLayout.cpp @@ -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)