diff --git a/Source/Editor/GUI/Docking/DockWindow.cs b/Source/Editor/GUI/Docking/DockWindow.cs index 0d58c00a5..257518ecf 100644 --- a/Source/Editor/GUI/Docking/DockWindow.cs +++ b/Source/Editor/GUI/Docking/DockWindow.cs @@ -241,7 +241,7 @@ namespace FlaxEditor.GUI.Docking /// public void FocusOrShow() { - FocusOrShow((DockState)Editor.Instance.Options.Options.Interface.NewWindowMethod); + FocusOrShow((DockState)Editor.Instance.Options.Options.Interface.NewWindowLocation); } /// diff --git a/Source/Editor/Modules/ContentEditingModule.cs b/Source/Editor/Modules/ContentEditingModule.cs index 6d0299a43..563143fc5 100644 --- a/Source/Editor/Modules/ContentEditingModule.cs +++ b/Source/Editor/Modules/ContentEditingModule.cs @@ -2,9 +2,9 @@ using System; using FlaxEditor.Content; +using FlaxEditor.GUI.Docking; using FlaxEditor.Windows; using FlaxEngine; -using DockState = FlaxEditor.GUI.Docking.DockState; namespace FlaxEditor.Modules { @@ -42,7 +42,6 @@ namespace FlaxEditor.Modules var proxy = Editor.ContentDatabase.GetProxy(item); if (proxy == null) { - // Error Editor.Log("Missing content proxy object for " + item); return null; } @@ -58,24 +57,31 @@ namespace FlaxEditor.Modules } if (window != null && !disableAutoShow) { - // Check if there is a floating window that has the same size - Vector2 defaultSize = window.DefaultSize; - for (var i = 0; i < Editor.UI.MasterPanel.FloatingPanels.Count; i++) + var newLocation = (DockState)Editor.Options.Options.Interface.NewWindowLocation; + if (newLocation == DockState.Float) { - var win = Editor.UI.MasterPanel.FloatingPanels[i]; - - // Check if size is similar - if (Vector2.Abs(win.Size - defaultSize).LengthSquared < 100) + // Check if there is a floating window that has the same size + Vector2 defaultSize = window.DefaultSize; + for (var i = 0; i < Editor.UI.MasterPanel.FloatingPanels.Count; i++) { - // Dock - window.Show(DockState.DockFill, win); - window.Focus(); - return window; - } - } + var win = Editor.UI.MasterPanel.FloatingPanels[i]; - // Show floating - window.ShowFloating(defaultSize); + // Check if size is similar + if (Vector2.Abs(win.Size - defaultSize).LengthSquared < 100) + { + // Dock + window.Show(DockState.DockFill, win); + window.Focus(); + return window; + } + } + + window.ShowFloating(defaultSize); + } + else + { + window.Show(newLocation); + } } return window; @@ -122,7 +128,7 @@ namespace FlaxEditor.Modules if (item is NewItem ni) { if (!ni.Proxy.IsFileNameValid(shortName)) - { + { hint = "Name does not follow " + ni.Proxy.Name + " name restrictions !"; return false; } @@ -131,7 +137,7 @@ namespace FlaxEditor.Modules { var proxy = Editor.ContentDatabase.GetProxy(item); if (proxy != null && !proxy.IsFileNameValid(shortName)) - { + { hint = "Name does not follow " + proxy.Name + " name restrictions !"; return false; } diff --git a/Source/Editor/Options/InterfaceOptions.cs b/Source/Editor/Options/InterfaceOptions.cs index eb0e0e762..c17260055 100644 --- a/Source/Editor/Options/InterfaceOptions.cs +++ b/Source/Editor/Options/InterfaceOptions.cs @@ -110,8 +110,8 @@ namespace FlaxEditor.Options /// Gets or sets the method window opening. /// [DefaultValue(DockStateProxy.Float)] - [EditorDisplay("Interface", "Define The Opening Window Method"), EditorOrder(150), Tooltip("Define the opening method for new windows, open in a new tab by default.")] - public DockStateProxy NewWindowMethod { get; set; } = DockStateProxy.Float; + [EditorDisplay("Interface", "New Window Location"), EditorOrder(150), Tooltip("Define the opening method for new windows, open in a new tab by default.")] + public DockStateProxy NewWindowLocation { get; set; } = DockStateProxy.Float; /// /// Gets or sets the timestamps prefix mode for debug log messages. diff --git a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs index 3cecb0ac4..50187ed3f 100644 --- a/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs +++ b/Source/Editor/Windows/SceneTreeWindow.ContextMenu.cs @@ -167,7 +167,7 @@ namespace FlaxEditor.Windows /// /// The parent control. /// The location (within a given control). - private void ShowContextMenu(Control parent, ref Vector2 location) + private void ShowContextMenu(Control parent, Vector2 location) { var contextMenu = CreateContextMenu(); diff --git a/Source/Editor/Windows/SceneTreeWindow.cs b/Source/Editor/Windows/SceneTreeWindow.cs index 6deb6d778..c327f709a 100644 --- a/Source/Editor/Windows/SceneTreeWindow.cs +++ b/Source/Editor/Windows/SceneTreeWindow.cs @@ -276,7 +276,7 @@ namespace FlaxEditor.Windows if (!Editor.StateMachine.CurrentState.CanEditScene) return; - ShowContextMenu(node, ref location); + ShowContextMenu(node, location); } /// @@ -379,7 +379,7 @@ namespace FlaxEditor.Windows { // Show context menu Editor.SceneEditing.Deselect(); - ShowContextMenu(this, ref location); + ShowContextMenu(Parent, location + _searchBox.BottomLeft); } return true; diff --git a/Source/Engine/Content/Assets/Model.h b/Source/Engine/Content/Assets/Model.h index e51ae5ff8..2134fb936 100644 --- a/Source/Engine/Content/Assets/Model.h +++ b/Source/Engine/Content/Assets/Model.h @@ -5,16 +5,6 @@ #include "ModelBase.h" #include "Engine/Graphics/Models/ModelLOD.h" -// Note: we use the first chunk as a header, next is the highest quality lod and then lower ones -// -// Example: -// Chunk 0: Header -// Chunk 1: LOD0 -// Chunk 2: LOD1 -// .. -// -#define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1) - class Mesh; class StreamModelLODTask; diff --git a/Source/Engine/Content/Assets/ModelBase.h b/Source/Engine/Content/Assets/ModelBase.h index 527a7ac61..4833c3244 100644 --- a/Source/Engine/Content/Assets/ModelBase.h +++ b/Source/Engine/Content/Assets/ModelBase.h @@ -7,6 +7,16 @@ #include "Engine/Graphics/Models/MaterialSlot.h" #include "Engine/Streaming/StreamableResource.h" +// Note: we use the first chunk as a header, next is the highest quality lod and then lower ones +// +// Example: +// Chunk 0: Header +// Chunk 1: LOD0 +// Chunk 2: LOD1 +// .. +// +#define MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1) + class MeshBase; /// @@ -58,7 +68,6 @@ public: /// /// Gets amount of the level of details in the model. /// - /// Amount of the level of details in the model. virtual int32 GetLODsCount() const = 0; /// diff --git a/Source/Engine/Content/Assets/SkinnedModel.cpp b/Source/Engine/Content/Assets/SkinnedModel.cpp index 1ce72f4b4..abd94b511 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.cpp +++ b/Source/Engine/Content/Assets/SkinnedModel.cpp @@ -147,13 +147,13 @@ Array SkinnedModel::GetBlendShapes() ContentLoadTask* SkinnedModel::RequestLODDataAsync(int32 lodIndex) { - const int32 chunkIndex = SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex); + const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); return RequestChunkDataAsync(chunkIndex); } void SkinnedModel::GetLODData(int32 lodIndex, BytesContainer& data) const { - const int32 chunkIndex = SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex); + const int32 chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(lodIndex); GetChunkData(chunkIndex, data); } @@ -617,7 +617,7 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path) } // Override meshes data chunk with the fetched GPU meshes memory - auto lodChunk = GET_CHUNK(SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex)); + auto lodChunk = GET_CHUNK(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)); if (lodChunk == nullptr) return true; lodChunk->Data.Copy(meshesStream.GetHandle(), meshesStream.GetPosition()); @@ -631,7 +631,7 @@ bool SkinnedModel::Save(bool withMeshDataFromGpu, const StringView& path) // Load all chunks with a mesh data for (int32 lodIndex = 0; lodIndex < LODs.Count(); lodIndex++) { - if (LoadChunk(SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lodIndex))) + if (LoadChunk(MODEL_LOD_TO_CHUNK_INDEX(lodIndex))) return true; } } diff --git a/Source/Engine/Content/Assets/SkinnedModel.h b/Source/Engine/Content/Assets/SkinnedModel.h index 08cbf23b0..32be6df3d 100644 --- a/Source/Engine/Content/Assets/SkinnedModel.h +++ b/Source/Engine/Content/Assets/SkinnedModel.h @@ -9,16 +9,6 @@ class StreamSkinnedModelLODTask; -// Note: we use the first chunk as a header, next is the highest quality lod and then lower ones -// -// Example: -// Chunk 0: Header -// Chunk 1: LOD0 -// Chunk 2: LOD1 -// .. -// -#define SKINNED_MODEL_LOD_TO_CHUNK_INDEX(lod) (lod + 1) - /// /// Skinned model asset that contains model object made of meshes that can be rendered on the GPU using skeleton bones skinning. /// diff --git a/Source/Engine/Core/Types/StringView.cpp b/Source/Engine/Core/Types/StringView.cpp index 8b30cc755..606e37ad1 100644 --- a/Source/Engine/Core/Types/StringView.cpp +++ b/Source/Engine/Core/Types/StringView.cpp @@ -12,9 +12,8 @@ StringView StringBuilder::ToStringView() const StringView StringView::Empty; StringView::StringView(const String& str) + : StringViewBase(str.Get(), str.Length()) { - _data = str.Get(); - _length = str.Length(); } bool StringView::operator==(const String& other) const @@ -74,9 +73,8 @@ bool operator!=(const String& a, const StringView& b) StringAnsiView StringAnsiView::Empty; StringAnsiView::StringAnsiView(const StringAnsi& str) + : StringViewBase(str.Get(), str.Length()) { - _data = str.Get(); - _length = str.Length(); } bool StringAnsiView::operator==(const StringAnsi& other) const diff --git a/Source/Engine/Core/Types/StringView.h b/Source/Engine/Core/Types/StringView.h index b630c429c..0ed2580ce 100644 --- a/Source/Engine/Core/Types/StringView.h +++ b/Source/Engine/Core/Types/StringView.h @@ -18,9 +18,15 @@ protected: int32 _length; constexpr StringViewBase() + : _data(nullptr) + , _length(0) + { + } + + constexpr StringViewBase(const T* data, int32 length) + : _data(data) + , _length(length) { - _data = nullptr; - _length = 0; } public: @@ -35,6 +41,16 @@ public: ASSERT(index >= 0 && index <= _length); return _data[index]; } + + FORCE_INLINE StringViewBase& operator=(const StringViewBase& other) + { + if (this != &other) + { + _data = other._data; + _length = other._length; + } + return *this; + } /// /// Lexicographically tests how this string compares to the other given string. @@ -194,6 +210,7 @@ public: /// Initializes a new instance of the class. /// constexpr StringView() + : StringViewBase() { } @@ -208,9 +225,8 @@ public: /// /// The reference to the static string. constexpr StringView(const StringView& str) + : StringViewBase(str._data, str._length) { - _data = str._data; - _length = str._length; } /// @@ -229,9 +245,8 @@ public: /// The characters sequence. /// The characters sequence length (excluding null-terminator character). constexpr StringView(const Char* str, int32 length) + : StringViewBase(str, length) { - _data = str; - _length = length; } public: @@ -248,21 +263,6 @@ public: return *this; } - /// - /// Assigns the static string. - /// - /// The other object. - /// The reference to this object. - FORCE_INLINE constexpr StringView& operator=(const StringView& other) - { - if (this != &other) - { - _data = other._data; - _length = other._length; - } - return *this; - } - /// /// Lexicographically test whether this string is equivalent to the other given string (case sensitive). /// @@ -399,6 +399,7 @@ public: /// Initializes a new instance of the class. /// constexpr StringAnsiView() + : StringViewBase() { } @@ -413,9 +414,8 @@ public: /// /// The reference to the static string. constexpr StringAnsiView(const StringAnsiView& str) + : StringViewBase(str._data, str._length) { - _data = str._data; - _length = str._length; } /// @@ -434,9 +434,8 @@ public: /// The characters sequence. /// The characters sequence length (excluding null-terminator character). constexpr StringAnsiView(const char* str, int32 length) + : StringViewBase(str, length) { - _data = str; - _length = length; } public: @@ -453,21 +452,6 @@ public: return *this; } - /// - /// Assigns the static string. - /// - /// The other object. - /// The reference to this object. - FORCE_INLINE constexpr StringAnsiView& operator=(const StringAnsiView& other) - { - if (this != &other) - { - _data = other._data; - _length = other._length; - } - return *this; - } - /// /// Lexicographically test whether this string is equivalent to the other given string (case sensitive). /// diff --git a/Source/Engine/Graphics/Models/BlendShape.cpp b/Source/Engine/Graphics/Models/BlendShape.cpp index 0b21a57cd..1a0a4e8aa 100644 --- a/Source/Engine/Graphics/Models/BlendShape.cpp +++ b/Source/Engine/Graphics/Models/BlendShape.cpp @@ -78,11 +78,11 @@ void BlendShapesInstance::Update(SkinnedModel* skinnedModel) if (!instance.IsUsed) continue; const SkinnedMesh* mesh = e.Key; - const int32 vertexCount = mesh->GetVertexCount(); // Get skinned mesh vertex buffer data (original, cached on CPU) BytesContainer vertexBuffer; - if (mesh->DownloadDataCPU(MeshBufferType::Vertex0, vertexBuffer)) + int32 vertexCount; + if (mesh->DownloadDataCPU(MeshBufferType::Vertex0, vertexBuffer, vertexCount)) { // Don't use this mesh if failed to get it's vertices data instance.IsUsed = false; diff --git a/Source/Engine/Graphics/Models/Mesh.cpp b/Source/Engine/Graphics/Models/Mesh.cpp index 3e0a47f4e..ad64a3f61 100644 --- a/Source/Engine/Graphics/Models/Mesh.cpp +++ b/Source/Engine/Graphics/Models/Mesh.cpp @@ -7,7 +7,7 @@ #include "Engine/Graphics/GPUContext.h" #include "Engine/Graphics/GPUDevice.h" #include "Engine/Graphics/RenderTask.h" -#include "Engine/Level/Scene/Scene.h" +#include "Engine/Profiler/ProfilerCPU.h" #include "Engine/Renderer/RenderList.h" #include "Engine/Serialization/MemoryReadStream.h" #include "Engine/Threading/Threading.h" @@ -293,6 +293,9 @@ bool Mesh::Load(uint32 vertices, uint32 triangles, void* vb0, void* vb1, void* v _triangles = triangles; _vertices = vertices; _use16BitIndexBuffer = use16BitIndexBuffer; + _cachedVertexBuffer[0].Clear(); + _cachedVertexBuffer[1].Clear(); + _cachedVertexBuffer[2].Clear(); return false; @@ -314,6 +317,10 @@ void Mesh::Unload() _triangles = 0; _vertices = 0; _use16BitIndexBuffer = false; + _cachedIndexBuffer.Resize(0); + _cachedVertexBuffer[0].Clear(); + _cachedVertexBuffer[1].Clear(); + _cachedVertexBuffer[2].Clear(); } bool Mesh::Intersects(const Ray& ray, const Matrix& world, float& distance, Vector3& normal) const @@ -506,13 +513,94 @@ Task* Mesh::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) co return buffer ? buffer->DownloadDataAsync(result) : nullptr; } -bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result) const +bool Mesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const { -#if !BUILD_RELEASE - // TODO: implement this - LOG(Error, "Mesh::DownloadDataCPU not implemented."); -#endif - return true; + if (_cachedVertexBuffer[0].IsEmpty()) + { + PROFILE_CPU(); + auto model = GetModel(); + ScopeLock lock(model->Locker); + if (model->IsVirtual()) + { + LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download"); + return true; + } + + // Fetch chunk with data from drive/memory + const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(GetLODIndex()); + if (model->LoadChunk(chunkIndex)) + return true; + const auto chunk = model->GetChunk(chunkIndex); + if (!chunk) + { + LOG(Error, "Missing chunk."); + return true; + } + + MemoryReadStream stream(chunk->Get(), chunk->Size()); + + // Seek to find mesh location + for (int32 i = 0; i <= _index; 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) + { + LOG(Error, "Invalid mesh data."); + return true; + } + auto vb0 = stream.Read(vertices); + auto vb1 = stream.Read(vertices); + bool hasColors = stream.ReadBool(); + VB2ElementType18* vb2 = nullptr; + if (hasColors) + { + vb2 = stream.Read(vertices); + } + auto ib = stream.Read(indicesCount * ibStride); + + if (i != _index) + continue; + + // Cache mesh data + _cachedIndexBufferCount = indicesCount; + _cachedIndexBuffer.Set(ib, indicesCount * ibStride); + _cachedVertexBuffer[0].Set((const byte*)vb0, vertices * sizeof(VB0ElementType)); + _cachedVertexBuffer[1].Set((const byte*)vb1, vertices * sizeof(VB1ElementType)); + if (hasColors) + _cachedVertexBuffer[2].Set((const byte*)vb2, vertices * sizeof(VB2ElementType)); + break; + } + } + + switch (type) + { + case MeshBufferType::Index: + result.Link(_cachedIndexBuffer); + count = _cachedIndexBufferCount; + break; + case MeshBufferType::Vertex0: + result.Link(_cachedVertexBuffer[0]); + count = _cachedVertexBuffer[0].Count() / sizeof(VB0ElementType); + break; + case MeshBufferType::Vertex1: + result.Link(_cachedVertexBuffer[1]); + count = _cachedVertexBuffer[1].Count() / sizeof(VB1ElementType); + break; + case MeshBufferType::Vertex2: + result.Link(_cachedVertexBuffer[2]); + count = _cachedVertexBuffer[2].Count() / sizeof(VB2ElementType); + break; + default: + return true; + } + return false; } ScriptingObject* Mesh::GetParentModel() @@ -645,145 +733,50 @@ bool Mesh::DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 typeI) } } - // Check if load data from GPU + MeshBufferType bufferType; + switch (type) + { + case InternalBufferType::VB0: + bufferType = MeshBufferType::Vertex0; + break; + case InternalBufferType::VB1: + bufferType = MeshBufferType::Vertex1; + break; + case InternalBufferType::VB2: + bufferType = MeshBufferType::Vertex2; + break; + case InternalBufferType::IB16: + case InternalBufferType::IB32: + bufferType = MeshBufferType::Index; + break; + default: + return true; + } + BytesContainer data; if (forceGpu) { - MeshBufferType bufferType; - switch (type) - { - case InternalBufferType::VB0: - bufferType = MeshBufferType::Vertex0; - break; - case InternalBufferType::VB1: - bufferType = MeshBufferType::Vertex1; - break; - case InternalBufferType::VB2: - bufferType = MeshBufferType::Vertex2; - break; - case InternalBufferType::IB16: - case InternalBufferType::IB32: - bufferType = MeshBufferType::Index; - break; - default: CRASH; - return true; - } - + // Get data from GPU // TODO: support reusing the input memory buffer to perform a single copy from staging buffer to the input CPU buffer - BytesContainer data; auto task = mesh->DownloadDataGPUAsync(bufferType, data); if (task == nullptr) return true; - - model->Locker.Unlock(); - task->Start(); + model->Locker.Unlock(); if (task->Wait()) { LOG(Error, "Task failed."); return true; } - - ConvertMeshData(mesh, type, resultObj, data.Get()); - model->Locker.Lock(); - - return false; } - - // Get data from drive/memory + else { - // Fetch chunk with data - const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(mesh->GetLODIndex()); - if (model->LoadChunk(chunkIndex)) + // Get data from CPU + int32 count; + if (DownloadDataCPU(bufferType, data, count)) return true; - const auto chunk = model->GetChunk(chunkIndex); - if (!chunk) - { - LOG(Error, "Missing chunk."); - return true; - } - - MemoryReadStream stream(chunk->Get(), chunk->Size()); - - // Seek to find mesh location - for (int32 i = 0; i < mesh->GetIndex(); 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) - { - LOG(Error, "Invalid mesh data."); - return true; - } - auto vb0 = stream.Read(vertices); - auto vb1 = stream.Read(vertices); - bool hasColors = stream.ReadBool(); - VB2ElementType18* vb2 = nullptr; - if (hasColors) - { - vb2 = stream.Read(vertices); - } - auto ib = stream.Read(indicesCount * ibStride); - } - - // Get mesh data - void* data = nullptr; - { - // #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) - { - LOG(Error, "Invalid mesh data."); - return true; - } - auto vb0 = stream.Read(vertices); - auto vb1 = stream.Read(vertices); - bool hasColors = stream.ReadBool(); - VB2ElementType18* vb2 = nullptr; - if (hasColors) - { - vb2 = stream.Read(vertices); - } - auto ib = stream.Read(indicesCount * ibStride); - - if (mesh->HasVertexColors() != hasColors || mesh->Use16BitIndexBuffer() != use16BitIndexBuffer) - { - LOG(Error, "Invalid mesh data loaded from chunk."); - return true; - } - - switch (type) - { - case InternalBufferType::VB0: - data = vb0; - break; - case InternalBufferType::VB1: - data = vb1; - break; - case InternalBufferType::VB2: - data = vb2; - break; - case InternalBufferType::IB16: - case InternalBufferType::IB32: - data = ib; - break; - } - } - - ConvertMeshData(mesh, type, resultObj, data); } + ConvertMeshData(mesh, type, resultObj, data.Get()); return false; } diff --git a/Source/Engine/Graphics/Models/Mesh.h b/Source/Engine/Graphics/Models/Mesh.h index ca5174f7b..4af23d3fe 100644 --- a/Source/Engine/Graphics/Models/Mesh.h +++ b/Source/Engine/Graphics/Models/Mesh.h @@ -22,6 +22,7 @@ API_CLASS(NoSpawn) class FLAXENGINE_API Mesh : public MeshBase { DECLARE_SCRIPTING_TYPE_WITH_CONSTRUCTOR_IMPL(Mesh, MeshBase); protected: + int32 _index; int32 _lodIndex; bool _hasLightmapUVs; @@ -30,8 +31,12 @@ protected: #if USE_PRECISE_MESH_INTERSECTS CollisionProxy _collisionProxy; #endif + mutable Array _cachedVertexBuffer[3]; + mutable Array _cachedIndexBuffer; + mutable int32 _cachedIndexBufferCount; public: + Mesh(const Mesh& other) : Mesh() { @@ -46,6 +51,7 @@ public: ~Mesh(); public: + /// /// Gets the model owning this mesh. /// @@ -92,7 +98,6 @@ public: /// /// Determines whether this mesh is initialized (has vertex and index buffers initialized). /// - /// True if this instance is initialized, otherwise false. FORCE_INLINE bool IsInitialized() const { return _vertexBuffers[0] != nullptr; @@ -101,13 +106,11 @@ public: /// /// Determines whether this mesh has a vertex colors buffer. /// - /// True if this mesh has a vertex colors buffers. API_PROPERTY() bool HasVertexColors() const; /// /// Determines whether this mesh contains valid lightmap texture coordinates data. /// - /// True if this mesh has a vertex colors buffers. API_PROPERTY() FORCE_INLINE bool HasLightmapUVs() const { return _hasLightmapUVs; @@ -118,7 +121,6 @@ public: /// /// Gets the collision proxy used by the mesh. /// - /// The collisions proxy container object reference. FORCE_INLINE const CollisionProxy& GetCollisionProxy() const { return _collisionProxy; @@ -127,6 +129,7 @@ public: #endif public: + /// /// Updates the model mesh (used by the virtual models created with Init rather than Load). /// @@ -205,6 +208,7 @@ public: bool UpdateMesh(uint32 vertexCount, uint32 triangleCount, Vector3* vertices, uint32* triangles, Vector3* normals = nullptr, Vector3* tangents = nullptr, Vector2* uvs = nullptr, Color32* colors = nullptr); public: + /// /// Updates the model mesh index buffer (used by the virtual models created with Init rather than Load). /// @@ -237,6 +241,7 @@ public: bool UpdateTriangles(uint32 triangleCount, void* ib, bool use16BitIndices); public: + /// /// Initializes instance of the class. /// @@ -268,6 +273,7 @@ public: void Unload(); public: + /// /// Determines if there is an intersection between the mesh and a ray in given world /// @@ -288,6 +294,7 @@ public: } public: + /// /// Gets the draw call geometry for this mesh. Sets the index and vertex buffers. /// @@ -387,12 +394,14 @@ public: void Draw(const RenderContext& renderContext, const DrawInfo& info, float lodDitherFactor) const; public: + // [MeshBase] bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const override; Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const override; - bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const override; + bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override; private: + // Internal bindings API_FUNCTION(NoProxy) ScriptingObject* GetParentModel(); API_FUNCTION(NoProxy) bool UpdateMeshInt(int32 vertexCount, int32 triangleCount, MonoArray* verticesObj, MonoArray* trianglesObj, MonoArray* normalsObj, MonoArray* tangentsObj, MonoArray* uvObj, MonoArray* colorsObj); diff --git a/Source/Engine/Graphics/Models/MeshBase.h b/Source/Engine/Graphics/Models/MeshBase.h index bedb57c90..7a17630c7 100644 --- a/Source/Engine/Graphics/Models/MeshBase.h +++ b/Source/Engine/Graphics/Models/MeshBase.h @@ -45,7 +45,6 @@ public: /// /// Gets the triangle count. /// - /// The triangles API_PROPERTY() FORCE_INLINE int32 GetTriangleCount() const { return _triangles; @@ -54,7 +53,6 @@ public: /// /// Gets the vertex count. /// - /// The vertices API_PROPERTY() FORCE_INLINE int32 GetVertexCount() const { return _vertices; @@ -63,7 +61,6 @@ public: /// /// Gets the box. /// - /// The bounding box. API_PROPERTY() FORCE_INLINE const BoundingBox& GetBox() const { return _box; @@ -72,7 +69,6 @@ public: /// /// Gets the sphere. /// - /// The bounding sphere. API_PROPERTY() FORCE_INLINE const BoundingSphere& GetSphere() const { return _sphere; @@ -129,6 +125,7 @@ public: /// /// Buffer type /// The result data + /// The amount of items inside the result buffer. /// True if failed, otherwise false - virtual bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const = 0; + virtual bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const = 0; }; diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.cpp b/Source/Engine/Graphics/Models/SkinnedMesh.cpp index b11ceb544..b5c67bff9 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMesh.cpp @@ -227,16 +227,21 @@ Task* SkinnedMesh::DownloadDataGPUAsync(MeshBufferType type, BytesContainer& res return buffer ? buffer->DownloadDataAsync(result) : nullptr; } -bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result) const +bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const { if (_cachedVertexBuffer.IsEmpty()) { PROFILE_CPU(); auto model = GetSkinnedModel(); ScopeLock lock(model->Locker); + if (model->IsVirtual()) + { + LOG(Error, "Cannot access CPU data of virtual models. Use GPU data download"); + return true; + } // Fetch chunk with data from drive/memory - const auto chunkIndex = _lodIndex + 1; + const auto chunkIndex = MODEL_LOD_TO_CHUNK_INDEX(_lodIndex); if (model->LoadChunk(chunkIndex)) return true; const auto chunk = model->GetChunk(chunkIndex); @@ -283,6 +288,7 @@ bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result) c continue; // Cache mesh data + _cachedIndexBufferCount = indicesCount; _cachedIndexBuffer.Set(ib, indicesCount * ibStride); _cachedVertexBuffer.Set((const byte*)vb0, vertices * sizeof(VB0SkinnedElementType)); break; @@ -293,9 +299,11 @@ bool SkinnedMesh::DownloadDataCPU(MeshBufferType type, BytesContainer& result) c { case MeshBufferType::Index: result.Link(_cachedIndexBuffer); + count = _cachedIndexBufferCount; break; case MeshBufferType::Vertex0: result.Link(_cachedVertexBuffer); + count = _cachedVertexBuffer.Count() / sizeof(VB0SkinnedElementType); break; default: return true; @@ -533,16 +541,19 @@ bool SkinnedMesh::DownloadBuffer(bool forceGpu, MonoArray* resultObj, int32 type if (task == nullptr) return true; task->Start(); + model->Locker.Unlock(); if (task->Wait()) { LOG(Error, "Task failed."); return true; } + model->Locker.Lock(); } else { // Get data from CPU - if (DownloadDataCPU(bufferType, data)) + int32 count; + if (DownloadDataCPU(bufferType, data, count)) return true; } diff --git a/Source/Engine/Graphics/Models/SkinnedMesh.h b/Source/Engine/Graphics/Models/SkinnedMesh.h index eb854189e..108f36b33 100644 --- a/Source/Engine/Graphics/Models/SkinnedMesh.h +++ b/Source/Engine/Graphics/Models/SkinnedMesh.h @@ -25,6 +25,7 @@ protected: GPUBuffer* _indexBuffer; mutable Array _cachedIndexBuffer; mutable Array _cachedVertexBuffer; + mutable int32 _cachedIndexBufferCount; public: @@ -46,7 +47,6 @@ public: /// /// Gets the skinned model owning this mesh. /// - /// The skinned model FORCE_INLINE SkinnedModel* GetSkinnedModel() const { return (SkinnedModel*)_model; @@ -55,7 +55,6 @@ public: /// /// Gets the mesh index. /// - /// The index FORCE_INLINE int32 GetIndex() const { return _index; @@ -64,7 +63,6 @@ public: /// /// Determines whether this mesh is initialized (has vertex and index buffers initialized). /// - /// True if this instance is initialized, otherwise false. FORCE_INLINE bool IsInitialized() const { return _vertexBuffer != nullptr; @@ -241,7 +239,7 @@ public: // [MeshBase] bool DownloadDataGPU(MeshBufferType type, BytesContainer& result) const override; Task* DownloadDataGPUAsync(MeshBufferType type, BytesContainer& result) const override; - bool DownloadDataCPU(MeshBufferType type, BytesContainer& result) const override; + bool DownloadDataCPU(MeshBufferType type, BytesContainer& result, int32& count) const override; private: diff --git a/Source/Engine/Level/Level.cpp b/Source/Engine/Level/Level.cpp index 5b99ba5ff..e97cf6b20 100644 --- a/Source/Engine/Level/Level.cpp +++ b/Source/Engine/Level/Level.cpp @@ -425,16 +425,19 @@ class UnloadSceneAction : public SceneAction { public: - Scene* TargetScene; + Guid TargetScene; UnloadSceneAction(Scene* scene) { - TargetScene = scene; + TargetScene = scene->GetID(); } bool Do() const override { - return unloadScene(TargetScene); + auto scene = Scripting::FindObject(TargetScene); + if (!scene) + return true; + return unloadScene(scene); } }; @@ -1344,6 +1347,7 @@ bool Level::UnloadScene(Scene* scene) void Level::UnloadSceneAsync(Scene* scene) { + CHECK(scene); ScopeLock lock(_sceneActionsLocker); _sceneActions.Enqueue(New(scene)); } diff --git a/Source/Engine/Physics/CollisionCooking.cpp b/Source/Engine/Physics/CollisionCooking.cpp index 052ac87d3..0f0c300de 100644 --- a/Source/Engine/Physics/CollisionCooking.cpp +++ b/Source/Engine/Physics/CollisionCooking.cpp @@ -168,14 +168,6 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali } else { - // Model data gather requires to use async tasks (main thread cannot stall) - // TODO: if asset is not virtual then maybe use the asset container and read raw bytes from file?? - if (IsInMainThread()) - { - LOG(Warning, "Cannot generate collision data on a main thread."); - return true; - } - // Ensure model is loaded if (arg.Model == nullptr) { @@ -193,51 +185,100 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali Array meshes; arg.Model->GetMeshes(meshes, lodIndex); - // Download model LOD data from the GPU. - // It's easier than reading internal, versioned mesh storage format. - // Also it works with virtual assets that have no dedicated storage. - // Note: request all meshes data at once and wait for the tasks to be done. + // Get mesh data const int32 meshesCount = meshes.Count(); Array vertexBuffers; Array indexBuffers; + Array vertexCounts; + Array indexCounts; vertexBuffers.Resize(meshesCount); + vertexCounts.Resize(meshesCount); indexBuffers.Resize(needIndexBuffer ? meshesCount : 0); - - // Start tasks - int32 vCount = 0; - int32 iCount = 0; - Array tasks(meshesCount + meshesCount); - for (int32 i = 0; i < meshesCount; i++) + indexCounts.Resize(needIndexBuffer ? meshesCount : 0); + bool useCpuData = IsInMainThread() && !arg.Model->IsVirtual(); + if (!useCpuData) { - const auto& mesh = *meshes[i]; - if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0) - continue; - - auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, vertexBuffers[i]); - if (task == nullptr) - return true; - task->Start(); - tasks.Add(task); - vCount += mesh.GetVertexCount(); - - if (needIndexBuffer) + // If mesh data is already cached in memory then we could use it instead of GPU + useCpuData |= arg.Model->HasChunkLoaded(MODEL_LOD_TO_CHUNK_INDEX(lodIndex)); + } + if (useCpuData) + { + // Read directly from the asset storage + for (int32 i = 0; i < meshesCount; i++) { - task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, indexBuffers[i]); - if (task == nullptr) + const auto& mesh = *meshes[i]; + if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0) + continue; + + int32 count; + if (mesh.DownloadDataCPU(MeshBufferType::Vertex0, vertexBuffers[i], count)) + { + LOG(Error, "Failed to download mesh {0} data from model {1} LOD{2}", i, arg.Model.ToString(), lodIndex); return true; - task->Start(); - tasks.Add(task); - iCount += mesh.GetTriangleCount() * 3; + } + vertexCounts[i] = count; + + if (needIndexBuffer) + { + if (mesh.DownloadDataCPU(MeshBufferType::Index, indexBuffers[i], count)) + { + LOG(Error, "Failed to download mesh {0} data from model {1} LOD{2}", i, arg.Model.ToString(), lodIndex); + return true; + } + indexCounts[i] = count; + } } } + else + { + // Download model LOD data from the GPU. + // It's easier than reading internal, versioned mesh storage format. + // Also it works with virtual assets that have no dedicated storage. + // Note: request all meshes data at once and wait for the tasks to be done. + Array tasks(meshesCount + meshesCount); + for (int32 i = 0; i < meshesCount; i++) + { + const auto& mesh = *meshes[i]; + if ((arg.MaterialSlotsMask & (1 << mesh.GetMaterialSlotIndex())) == 0) + continue; - // Wait for tasks - if (Task::WaitAll(tasks)) - return true; - tasks.Resize(0); + auto task = mesh.DownloadDataGPUAsync(MeshBufferType::Vertex0, vertexBuffers[i]); + if (task == nullptr) + { + LOG(Error, "Failed to download mesh {0} data from model {1} LOD{2}", i, arg.Model.ToString(), lodIndex); + return true; + } + int32 count = mesh.GetVertexCount(); + task->Start(); + tasks.Add(task); + vertexCounts[i] = count; + + if (needIndexBuffer) + { + task = mesh.DownloadDataGPUAsync(MeshBufferType::Index, indexBuffers[i]); + if (task == nullptr) + { + LOG(Error, "Failed to download mesh {0} data from model {1} LOD{2}", i, arg.Model.ToString(), lodIndex); + return true; + } + count = mesh.GetTriangleCount() * 3; + task->Start(); + tasks.Add(task); + indexCounts[i] = count; + } + } + if (Task::WaitAll(tasks)) + return true; + } // Combine meshes into one + int32 vCount = 0; + for (int32 i = 0; i < vertexCounts.Count(); i++) + vCount += vertexCounts[0]; finalVertexData.Allocate(vCount); + int32 iCount = 0; + for (int32 i = 0; i < indexCounts.Count(); i++) + iCount += indexCounts[0]; finalIndexData.Allocate(iCount); int32 vertexCounter = 0, indexCounter = 0; for (int32 i = 0; i < meshesCount; i++) @@ -248,15 +289,15 @@ bool CollisionCooking::CookCollision(const Argument& arg, CollisionData::Seriali const auto& vData = vertexBuffers[i]; const int32 firstVertexIndex = vertexCounter; - const int32 vertexCount = mesh.GetVertexCount(); + const int32 vertexCount = vertexCounts[i]; Platform::MemoryCopy(finalVertexData.Get() + firstVertexIndex, vData.Get(), vertexCount * sizeof(Vector3)); vertexCounter += vertexCount; if (needIndexBuffer) { const auto& iData = indexBuffers[i]; - const int32 indexCount = mesh.GetTriangleCount() * 3; - if (mesh.Use16BitIndexBuffer()) + const int32 indexCount = indexCounts[i]; + if (iData.Length() / indexCount == sizeof(uint16)) { auto dst = finalIndexData.Get() + indexCounter; auto src = iData.Get(); diff --git a/Source/Engine/Physics/CollisionData.h b/Source/Engine/Physics/CollisionData.h index 81368ec93..9436ac80d 100644 --- a/Source/Engine/Physics/CollisionData.h +++ b/Source/Engine/Physics/CollisionData.h @@ -197,13 +197,13 @@ public: #if COMPILE_WITH_PHYSICS_COOKING /// - /// Cooks the mesh collision data and updates the virtual asset. action cannot be performed on a main thread. + /// Cooks the mesh collision data and updates the virtual asset. /// /// /// Can be used only for virtual assets (see and ). /// /// The collision data type. - /// The source model. + /// The source model. If model is virtual then this method cannot be called from the main thread. /// The source model LOD index. /// The source model material slots mask. One bit per-slot. Can be used to exclude particular material slots from collision cooking. /// The convex mesh generation flags. diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index b44f3f452..08dff52a0 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -206,7 +206,7 @@ void LightPass::RenderLight(RenderContext& renderContext, GPUTextureView* lightB // Bind output GPUTexture* depthBuffer = renderContext.Buffers->DepthBuffer; - const bool depthBufferReadOnly = depthBuffer->GetDescription().Flags & GPUTextureFlags::ReadOnlyDepthView; + const bool depthBufferReadOnly = (depthBuffer->GetDescription().Flags & GPUTextureFlags::ReadOnlyDepthView) != 0; GPUTextureView* depthBufferRTV = depthBufferReadOnly ? depthBuffer->ViewReadOnlyDepth() : nullptr; GPUTextureView* depthBufferSRV = depthBufferReadOnly ? depthBuffer->ViewReadOnlyDepth() : depthBuffer->View(); context->SetRenderTarget(depthBufferRTV, lightBuffer); diff --git a/Source/ThirdParty/rapidjson/internal/biginteger.h b/Source/ThirdParty/rapidjson/internal/biginteger.h index 5e7e61688..75209d422 100644 --- a/Source/ThirdParty/rapidjson/internal/biginteger.h +++ b/Source/ThirdParty/rapidjson/internal/biginteger.h @@ -18,7 +18,11 @@ #include "../rapidjson.h" #if defined(_MSC_VER) && defined(_M_AMD64) -#include // for _umul128 +#if _MSC_VER <= 1900 +#include +#else +#include +#endif #pragma intrinsic(_umul128) #endif diff --git a/Source/ThirdParty/rapidjson/internal/diyfp.h b/Source/ThirdParty/rapidjson/internal/diyfp.h index 617b07968..1530c22e0 100644 --- a/Source/ThirdParty/rapidjson/internal/diyfp.h +++ b/Source/ThirdParty/rapidjson/internal/diyfp.h @@ -22,7 +22,11 @@ #include "../rapidjson.h" #if defined(_MSC_VER) && defined(_M_AMD64) +#if _MSC_VER <= 1900 +#include +#else #include +#endif #pragma intrinsic(_BitScanReverse64) #pragma intrinsic(_umul128) #endif