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