diff --git a/Source/Engine/Core/Collections/ConcurrentArray.h b/Source/Engine/Core/Collections/ConcurrentArray.h new file mode 100644 index 000000000..441abeae6 --- /dev/null +++ b/Source/Engine/Core/Collections/ConcurrentArray.h @@ -0,0 +1,309 @@ +// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Platform/Platform.h" +#include "Engine/Platform/CriticalSection.h" +#include "Engine/Core/Memory/Memory.h" +#include "Engine/Core/Memory/Allocation.h" + +/// +/// Template for dynamic array with variable capacity that support concurrent elements appending (atomic add). +/// +/// The type of elements in the array. +/// The type of memory allocator. +template +class ConcurrentArray +{ + friend ConcurrentArray; +public: + typedef T ItemType; + typedef typename AllocationType::template Data AllocationData; + +private: + volatile int64 _count; + volatile int64 _capacity; + AllocationData _allocation; + CriticalSection _locker; + +public: + /// + /// Initializes a new instance of the class. + /// + FORCE_INLINE ConcurrentArray() + : _count(0) + , _capacity(0) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The initial capacity. + ConcurrentArray(int32 capacity) + : _count(0) + , _capacity(capacity) + { + if (capacity > 0) + _allocation.Allocate(capacity); + } + + /// + /// Initializes a new instance of the class. + /// + /// The initial data. + /// The amount of items. + ConcurrentArray(const T* data, int32 length) + { + ASSERT(length >= 0); + _count = _capacity = length; + if (length > 0) + { + _allocation.Allocate(length); + Memory::ConstructItems(_allocation.Get(), data, length); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The other collection to copy. + ConcurrentArray(const ConcurrentArray& other) + { + _count = _capacity = other._count; + if (_capacity > 0) + { + _allocation.Allocate(_capacity); + Memory::ConstructItems(_allocation.Get(), other.Get(), (int32)other._count); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// The other collection to move. + ConcurrentArray(ConcurrentArray&& other) noexcept + { + _count = other._count; + _capacity = other._capacity; + other._count = 0; + other._capacity = 0; + _allocation.Swap(other._allocation); + } + + /// + /// The assignment operator that deletes the current collection of items and the copies items from the other array. + /// + /// The other collection to copy. + /// The reference to this. + ConcurrentArray& operator=(const ConcurrentArray& other) noexcept + { + if (this != &other) + { + Memory::DestructItems(_allocation.Get(), (int32)_count); + if (_capacity < other.Count()) + { + _allocation.Free(); + _capacity = other.Count(); + _allocation.Allocate(_capacity); + } + _count = other.Count(); + Memory::ConstructItems(_allocation.Get(), other.Get(), (int32)_count); + } + return *this; + } + + /// + /// The move assignment operator that deletes the current collection of items and the moves items from the other array. + /// + /// The other collection to move. + /// The reference to this. + ConcurrentArray& operator=(ConcurrentArray&& other) noexcept + { + if (this != &other) + { + Memory::DestructItems(_allocation.Get(), (int32)_count); + _allocation.Free(); + _count = other._count; + _capacity = other._capacity; + other._count = 0; + other._capacity = 0; + _allocation.Swap(other._allocation); + } + return *this; + } + + /// + /// Finalizes an instance of the class. + /// + ~ConcurrentArray() + { + Memory::DestructItems(_allocation.Get(), (int32)_count); + } + +public: + /// + /// Gets the amount of the items in the collection. + /// + FORCE_INLINE int32 Count() const + { + return (int32)Platform::AtomicRead((volatile int64*)&_count); + } + + /// + /// Gets the amount of the items that can be contained by collection without resizing. + /// + FORCE_INLINE int32 Capacity() const + { + return (int32)Platform::AtomicRead((volatile int64*)&_capacity); + } + + /// + /// Gets the critical section locking the collection during resizing. + /// + FORCE_INLINE const CriticalSection& Locker() const + { + return _locker; + } + + /// + /// Gets the pointer to the first item in the collection (linear allocation). + /// + FORCE_INLINE T* Get() + { + return _allocation.Get(); + } + + /// + /// Gets the pointer to the first item in the collection (linear allocation). + /// + FORCE_INLINE const T* Get() const + { + return _allocation.Get(); + } + + /// + /// Gets or sets the item at the given index. + /// + /// The reference to the item. + FORCE_INLINE T& operator[](int32 index) + { + ASSERT(index >= 0 && index < Count()); + return _allocation.Get()[index]; + } + + /// + /// Gets the item at the given index. + /// + /// The reference to the item. + FORCE_INLINE const T& operator[](int32 index) const + { + ASSERT(index >= 0 && index < Count()); + return _allocation.Get()[index]; + } + +public: + FORCE_INLINE T* begin() + { + return &_allocation.Get()[0]; + } + + FORCE_INLINE T* end() + { + return &_allocation.Get()[Count()]; + } + + FORCE_INLINE const T* begin() const + { + return &_allocation.Get()[0]; + } + + FORCE_INLINE const T* end() const + { + return &_allocation.Get()[Count()]; + } + +public: + /// + /// Clear the collection without changing its capacity. + /// + void Clear() + { + _locker.Lock(); + Memory::DestructItems(_allocation.Get(), (int32)_count); + _count = 0; + _locker.Unlock(); + } + + /// + /// Changes the capacity of the collection. + /// + /// The new capacity. + /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. + void SetCapacity(const int32 capacity, bool preserveContents = true) + { + _locker.Lock(); + if (capacity == _capacity) + return; + ASSERT(capacity >= 0); + const int32 count = preserveContents ? ((int32)_count < capacity ? (int32)_count : capacity) : 0; + _allocation.Relocate(capacity, (int32)_count, count); + _capacity = capacity; + _count = count; + _locker.Unlock(); + } + + /// + /// Resizes the collection to the specified size. If the size is equal or less to the current capacity no additional memory reallocation in performed. + /// + /// The new collection size. + /// True if preserve collection data when changing its size, otherwise collection after resize might not contain the previous data. + void Resize(int32 size, bool preserveContents = true) + { + _locker.Lock(); + if (_count > size) + { + Memory::DestructItems(_allocation.Get() + size, (int32)_count - size); + } + else + { + EnsureCapacity(size, preserveContents); + Memory::ConstructItems(_allocation.Get() + _count, size - (int32)_count); + } + _count = size; + _locker.Unlock(); + } + + /// + /// Ensures the collection has given capacity (or more). + /// + /// The minimum capacity. + /// True if preserve collection data when changing its size, otherwise collection after resize will be empty. + void EnsureCapacity(int32 minCapacity, bool preserveContents = true) + { + _locker.Lock(); + if (_capacity < minCapacity) + { + const int32 capacity = _allocation.CalculateCapacityGrow((int32)_capacity, minCapacity); + SetCapacity(capacity, preserveContents); + } + _locker.Unlock(); + } + + /// + /// Adds the specified item to the collection. + /// + /// The item to add. + /// Index of the added element. + int32 Add(const T& item) + { + const int32 count = (int32)Platform::AtomicRead(&_count); + const int32 capacity = (int32)Platform::AtomicRead(&_capacity); + const int32 minCapacity = count + PLATFORM_THREADS_LIMIT; // Ensure there is a room for all threads (eg. all possible threads add item at once) + if (minCapacity >= capacity) + EnsureCapacity(minCapacity); + const int32 index = (int32)Platform::InterlockedIncrement(&_count) - 1; + Memory::ConstructItems(_allocation.Get() + index, &item, 1); + return index; + } +}; diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp index ffe24bb95..6f620cea0 100644 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp +++ b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.cpp @@ -84,12 +84,3 @@ void SkinnedMeshDrawData::OnDataChanged(bool dropHistory) _isDirty = true; _hasValidData = true; } - -void SkinnedMeshDrawData::Flush(GPUContext* context) -{ - if (_isDirty) - { - _isDirty = false; - context->UpdateBuffer(BoneMatrices, Data.Get(), Data.Count()); - } -} diff --git a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h index 2176fc7f1..9c899353f 100644 --- a/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h +++ b/Source/Engine/Graphics/Models/SkinnedMeshDrawData.h @@ -55,6 +55,14 @@ public: return BoneMatrices != nullptr && BoneMatrices->IsAllocated(); } + /// + /// Determines whether this instance has been modified and needs to be flushed with GPU buffer. + /// + FORCE_INLINE bool IsDirty() const + { + return _isDirty; + } + /// /// Setups the data container for the specified bones amount. /// @@ -75,8 +83,10 @@ public: void OnDataChanged(bool dropHistory); /// - /// Flushes the bones data buffer with the GPU by sending the data fro the CPU. + /// After bones Data has been send to the GPU buffer. /// - /// The GPU context. - void Flush(class GPUContext* context); + void OnFlush() + { + _isDirty = false; + } }; diff --git a/Source/Engine/Graphics/RenderTask.cpp b/Source/Engine/Graphics/RenderTask.cpp index d1b6b1e24..c1e9fc5d5 100644 --- a/Source/Engine/Graphics/RenderTask.cpp +++ b/Source/Engine/Graphics/RenderTask.cpp @@ -27,6 +27,7 @@ CriticalSection RenderTask::TasksLocker; int32 RenderTask::TasksDoneLastFrame; Array SceneRenderTask::GlobalCustomPostFx; MainRenderTask* MainRenderTask::Instance; +CriticalSection RenderContext::GPULocker; PostProcessEffect::PostProcessEffect(const SpawnParams& params) : Script(params) diff --git a/Source/Engine/Graphics/RenderTask.h b/Source/Engine/Graphics/RenderTask.h index c9a7aa7eb..072a87135 100644 --- a/Source/Engine/Graphics/RenderTask.h +++ b/Source/Engine/Graphics/RenderTask.h @@ -436,6 +436,11 @@ API_STRUCT(NoDefault) struct RenderContext /// API_FIELD() RenderView View; + /// + /// The GPU access locking critical section to protect data access when performing multi-threaded rendering. + /// + static CriticalSection GPULocker; + RenderContext() = default; RenderContext(SceneRenderTask* task) noexcept; }; @@ -462,6 +467,11 @@ API_STRUCT(NoDefault) struct RenderContextBatch /// API_FIELD() Array Contexts; + /// + /// The Job System labels to wait on, after draw calls collecting. + /// + API_FIELD() Array> WaitLabels; + RenderContextBatch() = default; RenderContextBatch(SceneRenderTask* task); RenderContextBatch(const RenderContext& context); diff --git a/Source/Engine/Level/Actors/AnimatedModel.cpp b/Source/Engine/Level/Actors/AnimatedModel.cpp index d8692b210..61243b1a3 100644 --- a/Source/Engine/Level/Actors/AnimatedModel.cpp +++ b/Source/Engine/Level/Actors/AnimatedModel.cpp @@ -555,7 +555,7 @@ void AnimatedModel::UpdateBounds() BoundingBox::Transform(_boxLocal, _transform, _box); BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } void AnimatedModel::UpdateSockets() @@ -713,7 +713,13 @@ void AnimatedModel::Draw(RenderContext& renderContext) _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.Position + renderContext.View.Origin)); if (_skinningData.IsReady()) { - _skinningData.Flush(GPUDevice::Instance->GetMainContext()); + // Flush skinning data with GPU + if (_skinningData.IsDirty()) + { + RenderContext::GPULocker.Lock(); + GPUDevice::Instance->GetMainContext()->UpdateBuffer(_skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count()); + RenderContext::GPULocker.Unlock(); + } SkinnedMesh::DrawInfo draw; draw.Buffer = &Entries; @@ -749,7 +755,13 @@ void AnimatedModel::Draw(RenderContextBatch& renderContextBatch) _lastMinDstSqr = Math::Min(_lastMinDstSqr, Vector3::DistanceSquared(_transform.Translation, renderContext.View.Position + renderContext.View.Origin)); if (_skinningData.IsReady()) { - _skinningData.Flush(GPUDevice::Instance->GetMainContext()); + // Flush skinning data with GPU + if (_skinningData.IsDirty()) + { + RenderContext::GPULocker.Lock(); + GPUDevice::Instance->GetMainContext()->UpdateBuffer(_skinningData.BoneMatrices, _skinningData.Data.Get(), _skinningData.Data.Count()); + RenderContext::GPULocker.Unlock(); + } SkinnedMesh::DrawInfo draw; draw.Buffer = &Entries; @@ -948,7 +960,7 @@ void AnimatedModel::OnTransformChanged() BoundingBox::Transform(_boxLocal, _transform, _box); BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } void AnimatedModel::WaitForModelLoad() diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.cpp b/Source/Engine/Level/Actors/ModelInstanceActor.cpp index a5fb15c0e..d5199a320 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.cpp +++ b/Source/Engine/Level/Actors/ModelInstanceActor.cpp @@ -20,7 +20,7 @@ void ModelInstanceActor::SetEntries(const Array& value) Entries[i] = value[i]; } if (anyChanged && _sceneRenderingKey != -1) - GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } void ModelInstanceActor::SetMaterial(int32 entryIndex, MaterialBase* material) @@ -33,7 +33,7 @@ void ModelInstanceActor::SetMaterial(int32 entryIndex, MaterialBase* material) return; Entries[entryIndex].Material = material; if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 entryIndex) @@ -45,7 +45,7 @@ MaterialInstance* ModelInstanceActor::CreateAndSetVirtualMaterialInstance(int32 const auto result = material->CreateVirtualInstance(); Entries[entryIndex].Material = result; if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); return result; } @@ -56,12 +56,12 @@ void ModelInstanceActor::WaitForModelLoad() void ModelInstanceActor::OnLayerChanged() { if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } void ModelInstanceActor::OnEnable() { - GetSceneRendering()->AddActor(this, _sceneRenderingKey); + GetSceneRendering()->AddActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); // Base Actor::OnEnable(); @@ -72,5 +72,5 @@ void ModelInstanceActor::OnDisable() // Base Actor::OnDisable(); - GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } diff --git a/Source/Engine/Level/Actors/ModelInstanceActor.h b/Source/Engine/Level/Actors/ModelInstanceActor.h index 84a45b1c4..b1fdc706f 100644 --- a/Source/Engine/Level/Actors/ModelInstanceActor.h +++ b/Source/Engine/Level/Actors/ModelInstanceActor.h @@ -13,7 +13,7 @@ API_CLASS(Abstract) class FLAXENGINE_API ModelInstanceActor : public Actor { DECLARE_SCENE_OBJECT_ABSTRACT(ModelInstanceActor); protected: - int32 _sceneRenderingKey = -1; + int32 _sceneRenderingKey = -1; // Uses SceneRendering::DrawCategory::SceneDrawAsync public: /// diff --git a/Source/Engine/Level/Actors/SplineModel.cpp b/Source/Engine/Level/Actors/SplineModel.cpp index e1e529631..af84d1d55 100644 --- a/Source/Engine/Level/Actors/SplineModel.cpp +++ b/Source/Engine/Level/Actors/SplineModel.cpp @@ -210,7 +210,7 @@ void SplineModel::OnSplineUpdated() BoundingSphere::Merge(_sphere, _instances[i].Sphere, _sphere); BoundingBox::FromSphere(_sphere, _box); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } void SplineModel::UpdateDeformationBuffer() @@ -309,8 +309,7 @@ void SplineModel::UpdateDeformationBuffer() } // Flush data with GPU - auto context = GPUDevice::Instance->GetMainContext(); - context->UpdateBuffer(_deformationBuffer, _deformationBufferData, size); + GPUDevice::Instance->GetMainContext()->UpdateBuffer(_deformationBuffer, _deformationBufferData, size); // Static splines are rarely updated so release scratch memory if (IsTransformStatic()) @@ -359,7 +358,11 @@ void SplineModel::Draw(RenderContext& renderContext) // Build mesh deformation buffer for the whole spline if (_deformationDirty) + { + RenderContext::GPULocker.Lock(); UpdateDeformationBuffer(); + RenderContext::GPULocker.Unlock(); + } // Draw all segments DrawCall drawCall; diff --git a/Source/Engine/Level/Actors/StaticModel.cpp b/Source/Engine/Level/Actors/StaticModel.cpp index 49af285d3..e6f8de72e 100644 --- a/Source/Engine/Level/Actors/StaticModel.cpp +++ b/Source/Engine/Level/Actors/StaticModel.cpp @@ -173,7 +173,7 @@ void StaticModel::OnModelChanged() if (Model && !Model->IsLoaded()) UpdateBounds(); else if (!Model && _sceneRenderingKey != -1) - GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } void StaticModel::OnModelLoaded() @@ -190,7 +190,7 @@ void StaticModel::OnModelLoaded() } else { - GetSceneRendering()->AddActor(this, _sceneRenderingKey); + GetSceneRendering()->AddActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } } } @@ -199,7 +199,7 @@ void StaticModel::OnModelResidencyChanged() { if (_sceneRenderingKey == -1 && _scene && Model && Model->GetLoadedLODs() > 0 && _residencyChangedModel) { - GetSceneRendering()->AddActor(this, _sceneRenderingKey); + GetSceneRendering()->AddActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); _residencyChangedModel->ResidencyChanged.Unbind(this); _residencyChangedModel = nullptr; } @@ -219,11 +219,12 @@ void StaticModel::UpdateBounds() } BoundingSphere::FromBox(_box, _sphere); if (_sceneRenderingKey != -1) - GetSceneRendering()->UpdateActor(this, _sceneRenderingKey); + GetSceneRendering()->UpdateActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } void StaticModel::FlushVertexColors() { + RenderContext::GPULocker.Lock(); for (int32 lodIndex = 0; lodIndex < _vertexColorsCount; lodIndex++) { auto& vertexColorsData = _vertexColorsData[lodIndex]; @@ -236,7 +237,7 @@ void StaticModel::FlushVertexColors() if (vertexColorsBuffer->GetSize() != size) { if (vertexColorsBuffer->Init(GPUBufferDescription::Vertex(sizeof(Color32), vertexColorsData.Count()))) - return; + break; } GPUDevice::Instance->GetMainContext()->UpdateBuffer(vertexColorsBuffer, vertexColorsData.Get(), size); } @@ -245,6 +246,7 @@ void StaticModel::FlushVertexColors() SAFE_DELETE_GPU_RESOURCE(vertexColorsBuffer); } } + RenderContext::GPULocker.Unlock(); } bool StaticModel::HasContentLoaded() const @@ -559,7 +561,7 @@ void StaticModel::OnEnable() } else { - GetSceneRendering()->AddActor(this, _sceneRenderingKey); + GetSceneRendering()->AddActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } } @@ -574,7 +576,7 @@ void StaticModel::OnDisable() if (_sceneRenderingKey != -1) { - GetSceneRendering()->RemoveActor(this, _sceneRenderingKey); + GetSceneRendering()->RemoveActor(this, _sceneRenderingKey, SceneRendering::SceneDrawAsync); } if (_residencyChangedModel) { diff --git a/Source/Engine/Level/Scene/SceneRendering.cpp b/Source/Engine/Level/Scene/SceneRendering.cpp index 5718d9b6c..e36f6c49e 100644 --- a/Source/Engine/Level/Scene/SceneRendering.cpp +++ b/Source/Engine/Level/Scene/SceneRendering.cpp @@ -1,25 +1,14 @@ // Copyright (c) 2012-2022 Wojciech Figat. All rights reserved. -#define SCENE_RENDERING_USE_PROFILER 0 +#define SCENE_RENDERING_USE_PROFILER_PER_ACTOR 0 #include "SceneRendering.h" #include "Engine/Graphics/RenderTask.h" #include "Engine/Graphics/RenderView.h" #include "Engine/Renderer/RenderList.h" +#include "Engine/Threading/JobSystem.h" #include "Engine/Threading/Threading.h" -#if SCENE_RENDERING_USE_PROFILER #include "Engine/Profiler/ProfilerCPU.h" -#endif - -FORCE_INLINE bool FrustumsListCull(const BoundingSphere& bounds, const BoundingFrustum* frustums, int32 frustumsCount) -{ - for (int32 i = 0; i < frustumsCount; i++) - { - if (frustums[i].Intersects(bounds)) - return true; - } - return false; -} ISceneRenderingListener::~ISceneRenderingListener() { @@ -38,99 +27,65 @@ void ISceneRenderingListener::ListenSceneRendering(SceneRendering* scene) } } +FORCE_INLINE bool FrustumsListCull(const BoundingSphere& bounds, const Array& frustums) +{ + const int32 count = frustums.Count(); + const BoundingFrustum* data = frustums.Get(); + for (int32 i = 0; i < count; i++) + { + if (data[i].Intersects(bounds)) + return true; + } + return false; +} + void SceneRendering::Draw(RenderContextBatch& renderContextBatch, DrawCategory category) { ScopeLock lock(Locker); auto& view = renderContextBatch.GetMainContext().View; - const Vector3 origin = view.Origin; for (auto& renderContext : renderContextBatch.Contexts) renderContext.List->Scenes.Add(this); auto& list = Actors[(int32)category]; + _drawListData = list.Get(); + _drawListSize = list.Count(); + _drawBatch = &renderContextBatch; // Setup frustum data - Array frustumsData; - BoundingFrustum* frustums = &view.CullingFrustum; - int32 frustumsCount = renderContextBatch.Contexts.Count(); + auto& frustumsData = _drawFrustumsData; + const int32 frustumsCount = renderContextBatch.Contexts.Count(); if (frustumsCount != 1) { frustumsData.Resize(frustumsCount); - frustums = frustumsData.Get(); for (int32 i = 0; i < frustumsCount; i++) - frustums[i] = renderContextBatch.Contexts[i].View.CullingFrustum; + frustumsData.Get()[i] = renderContextBatch.Contexts[i].View.CullingFrustum; } -#define CHECK_ACTOR ((view.RenderLayersMask.Mask & e.LayerMask) && (e.NoCulling || FrustumsListCull(e.Bounds, frustums, frustumsCount))) -#define CHECK_ACTOR_SINGLE_FRUSTUM ((view.RenderLayersMask.Mask & e.LayerMask) && (e.NoCulling || frustums->Intersects(e.Bounds))) -#if SCENE_RENDERING_USE_PROFILER -#define DRAW_ACTOR(mode) PROFILE_CPU_ACTOR(e.Actor); e.Actor->Draw(mode) -#else -#define DRAW_ACTOR(mode) e.Actor->Draw(mode) -#endif - // Draw all visual components - if (view.IsOfflinePass) + _drawListIndex = -1; + if (_drawListSize >= 64 && category == SceneDrawAsync) { - // Offline pass with additional static flags culling - for (int32 i = 0; i < list.Count(); i++) - { - auto e = list.Get()[i]; - e.Bounds.Center -= origin; - if (CHECK_ACTOR && e.Actor->GetStaticFlags() & view.StaticFlagsMask) - { - DRAW_ACTOR(renderContextBatch); - } - } - } - else if (origin.IsZero() && frustumsCount == 1) - { - // Fast path for no origin shifting with a single context - RenderContext& renderContext = renderContextBatch.GetMainContext(); - for (int32 i = 0; i < list.Count(); i++) - { - auto e = list.Get()[i]; - if (CHECK_ACTOR_SINGLE_FRUSTUM) - { - DRAW_ACTOR(renderContext); - } - } - } - else if (origin.IsZero()) - { - // Fast path for no origin shifting - for (int32 i = 0; i < list.Count(); i++) - { - auto e = list.Get()[i]; - if (CHECK_ACTOR) - { - DRAW_ACTOR(renderContextBatch); - } - } + // Run in async via Job System + Function func; + func.Bind(this); + const uint64 waitLabel = JobSystem::Dispatch(func, JobSystem::GetThreadsCount()); + renderContextBatch.WaitLabels.Add(waitLabel); } else { - // Generic case - for (int32 i = 0; i < list.Count(); i++) - { - auto e = list.Get()[i]; - e.Bounds.Center -= origin; - if (CHECK_ACTOR) - { - DRAW_ACTOR(renderContextBatch); - } - } + // Scene is small so draw on a main-thread + DrawActorsJob(0); } -#undef CHECK_ACTOR -#undef DRAW_ACTOR #if USE_EDITOR if (view.Pass & DrawPass::GBuffer && category == SceneDraw) { // Draw physics shapes if (view.Flags & ViewFlags::PhysicsDebug || view.Mode == ViewMode::PhysicsColliders) { + const PhysicsDebugCallback* physicsDebugData = PhysicsDebug.Get(); for (int32 i = 0; i < PhysicsDebug.Count(); i++) { - PhysicsDebug[i](view); + physicsDebugData[i](view); } } } @@ -144,7 +99,7 @@ void SceneRendering::CollectPostFxVolumes(RenderContext& renderContext) #endif for (int32 i = 0; i < PostFxProviders.Count(); i++) { - PostFxProviders[i]->Collect(renderContext); + PostFxProviders.Get()[i]->Collect(renderContext); } } @@ -174,7 +129,7 @@ void SceneRendering::AddActor(Actor* a, int32& key, DrawCategory category) key = 0; for (; key < list.Count(); key++) { - if (list[key].Actor == nullptr) + if (list.Get()[key].Actor == nullptr) break; } if (key == list.Count()) @@ -217,3 +172,65 @@ void SceneRendering::RemoveActor(Actor* a, int32& key, DrawCategory category) } key = -1; } + +#define FOR_EACH_BATCH_ACTOR const int64 count = _drawListSize; while (true) { const int64 index = Platform::InterlockedIncrement(&_drawListIndex); if (index >= count) break; auto e = _drawListData[index]; +#define CHECK_ACTOR ((view.RenderLayersMask.Mask & e.LayerMask) && (e.NoCulling || FrustumsListCull(e.Bounds, _drawFrustumsData))) +#define CHECK_ACTOR_SINGLE_FRUSTUM ((view.RenderLayersMask.Mask & e.LayerMask) && (e.NoCulling || view.CullingFrustum.Intersects(e.Bounds))) +#if SCENE_RENDERING_USE_PROFILER_PER_ACTOR +#define DRAW_ACTOR(mode) PROFILE_CPU_ACTOR(e.Actor); e.Actor->Draw(mode) +#else +#define DRAW_ACTOR(mode) e.Actor->Draw(mode) +#endif + +void SceneRendering::DrawActorsJob(int32 i) +{ + PROFILE_CPU(); + auto& mainContext = _drawBatch->GetMainContext(); + const auto& view = mainContext.View; + if (view.IsOfflinePass) + { + // Offline pass with additional static flags culling + FOR_EACH_BATCH_ACTOR + e.Bounds.Center -= view.Origin; + if (CHECK_ACTOR && e.Actor->GetStaticFlags() & view.StaticFlagsMask) + { + DRAW_ACTOR(*_drawBatch); + } + } + } + else if (view.Origin.IsZero() && _drawFrustumsData.Count() == 1) + { + // Fast path for no origin shifting with a single context + FOR_EACH_BATCH_ACTOR + if (CHECK_ACTOR_SINGLE_FRUSTUM) + { + DRAW_ACTOR(mainContext); + } + } + } + else if (view.Origin.IsZero()) + { + // Fast path for no origin shifting + FOR_EACH_BATCH_ACTOR + if (CHECK_ACTOR) + { + DRAW_ACTOR(*_drawBatch); + } + } + } + else + { + // Generic case + FOR_EACH_BATCH_ACTOR + e.Bounds.Center -= view.Origin; + if (CHECK_ACTOR) + { + DRAW_ACTOR(*_drawBatch); + } + } + } +} + +#undef FOR_EACH_BATCH_ACTOR +#undef CHECK_ACTOR +#undef DRAW_ACTOR diff --git a/Source/Engine/Level/Scene/SceneRendering.h b/Source/Engine/Level/Scene/SceneRendering.h index c94149b6f..a8567a642 100644 --- a/Source/Engine/Level/Scene/SceneRendering.h +++ b/Source/Engine/Level/Scene/SceneRendering.h @@ -5,6 +5,7 @@ #include "Engine/Core/Delegate.h" #include "Engine/Core/Collections/Array.h" #include "Engine/Core/Math/BoundingSphere.h" +#include "Engine/Core/Math/BoundingFrustum.h" #include "Engine/Level/Actor.h" #include "Engine/Platform/CriticalSection.h" @@ -81,6 +82,7 @@ public: enum DrawCategory { SceneDraw = 0, + SceneDrawAsync, PreRender, PostRender, MAX @@ -161,4 +163,13 @@ public: ViewportIcons.Remove(obj); } #endif + +private: + Array _drawFrustumsData; + DrawActor* _drawListData; + int64 _drawListSize; + volatile int64 _drawListIndex; + RenderContextBatch* _drawBatch; + + void DrawActorsJob(int32 i); }; diff --git a/Source/Engine/Particles/Particles.cpp b/Source/Engine/Particles/Particles.cpp index 3c8cdcb5f..0df69f100 100644 --- a/Source/Engine/Particles/Particles.cpp +++ b/Source/Engine/Particles/Particles.cpp @@ -581,7 +581,7 @@ void DrawEmitterGPU(RenderContext& renderContext, ParticleBuffer* buffer, DrawCa // Check if need to perform any particles sorting if (emitter->Graph.SortModules.HasItems() && renderContext.View.Pass != DrawPass::Depth) { - PROFILE_GPU_CPU("Sort Particles"); + PROFILE_GPU_CPU_NAMED("Sort Particles"); // Prepare pipeline if (GPUParticlesSorting == nullptr) diff --git a/Source/Engine/Renderer/Editor/MaterialComplexity.cpp b/Source/Engine/Renderer/Editor/MaterialComplexity.cpp index 4db975cf8..7fefb2a47 100644 --- a/Source/Engine/Renderer/Editor/MaterialComplexity.cpp +++ b/Source/Engine/Renderer/Editor/MaterialComplexity.cpp @@ -136,7 +136,7 @@ void MaterialComplexityMaterialShader::Draw(RenderContext& renderContext, GPUCon auto& decalsWrapper = _wrappers[4]; if (decals.HasItems() && boxModel && boxModel->CanBeRendered() && decalsWrapper.IsReady()) { - PROFILE_GPU_CPU("Decals"); + PROFILE_GPU_CPU_NAMED("Decals"); DrawCall drawCall; MaterialBase::BindParameters bindParams(context, renderContext, drawCall); drawCall.WorldDeterminantSign = 1.0f; @@ -166,14 +166,14 @@ void MaterialComplexityMaterialShader::Draw(RenderContext& renderContext, GPUCon auto& distortionList = renderContext.List->DrawCallsLists[(int32)DrawCallsListType::Distortion]; if (!distortionList.IsEmpty()) { - PROFILE_GPU_CPU("Distortion"); + PROFILE_GPU_CPU_NAMED("Distortion"); renderContext.View.Pass = DrawPass::Distortion; renderContext.List->ExecuteDrawCalls(renderContext, distortionList); } auto& forwardList = renderContext.List->DrawCallsLists[(int32)DrawCallsListType::Forward]; if (!forwardList.IsEmpty()) { - PROFILE_GPU_CPU("Forward"); + PROFILE_GPU_CPU_NAMED("Forward"); renderContext.View.Pass = DrawPass::Forward; renderContext.List->ExecuteDrawCalls(renderContext, forwardList); } diff --git a/Source/Engine/Renderer/GBufferPass.cpp b/Source/Engine/Renderer/GBufferPass.cpp index 9a9a64003..9f7f331af 100644 --- a/Source/Engine/Renderer/GBufferPass.cpp +++ b/Source/Engine/Renderer/GBufferPass.cpp @@ -158,7 +158,7 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer // Clear GBuffer { - PROFILE_GPU_CPU("Clear"); + PROFILE_GPU_CPU_NAMED("Clear"); context->ClearDepth(*renderContext.Buffers->DepthBuffer); context->Clear(lightBuffer, Color::Transparent); @@ -222,7 +222,7 @@ void GBufferPass::Fill(RenderContext& renderContext, GPUTextureView* lightBuffer // Draw sky if (renderContext.List->Sky && _skyModel && _skyModel->CanBeRendered()) { - PROFILE_GPU_CPU("Sky"); + PROFILE_GPU_CPU_NAMED("Sky"); DrawSky(renderContext, context); } diff --git a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp index 5bd0167f2..81bfbe831 100644 --- a/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp +++ b/Source/Engine/Renderer/GI/GlobalSurfaceAtlasPass.cpp @@ -482,16 +482,20 @@ bool GlobalSurfaceAtlasPass::Render(RenderContext& renderContext, GPUContext* co const float minObjectRadius = 20.0f; // Skip too small objects _cullingPosDistance = Vector4(viewPosition, distance); int32 actorsDrawn = 0; + SceneRendering::DrawCategory drawCategories[] = { SceneRendering::SceneDraw, SceneRendering::SceneDrawAsync }; for (auto* scene : renderContext.List->Scenes) { - auto& list = scene->Actors[SceneRendering::SceneDraw]; - for (auto& e : list) + for (SceneRendering::DrawCategory drawCategory : drawCategories) { - if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition) < distance) + auto& list = scene->Actors[drawCategory]; + for (auto& e : list) { - //PROFILE_CPU_ACTOR(e.Actor); - e.Actor->Draw(renderContext); - actorsDrawn++; + if (e.Bounds.Radius >= minObjectRadius && viewMask & e.LayerMask && CollisionsHelper::DistanceSpherePoint(e.Bounds, viewPosition) < distance) + { + //PROFILE_CPU_ACTOR(e.Actor); + e.Actor->Draw(renderContext); + actorsDrawn++; + } } } } diff --git a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp index b8e7dd204..2f2683196 100644 --- a/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp +++ b/Source/Engine/Renderer/GlobalSignDistanceFieldPass.cpp @@ -549,16 +549,20 @@ bool GlobalSignDistanceFieldPass::Render(RenderContext& renderContext, GPUContex BoundingBox cascadeBoundsWorld = cascadeBounds.MakeOffsetted(sdfData.Origin); _cascadeCullingBounds = cascadeBoundsWorld; int32 actorsDrawn = 0; - for (SceneRendering* scene : renderContext.List->Scenes) + SceneRendering::DrawCategory drawCategories[] = { SceneRendering::SceneDraw, SceneRendering::SceneDrawAsync }; + for (auto* scene : renderContext.List->Scenes) { - auto& list = scene->Actors[SceneRendering::SceneDraw]; - for (const auto& e : list) + for (SceneRendering::DrawCategory drawCategory : drawCategories) { - if (viewMask & e.LayerMask && e.Bounds.Radius >= minObjectRadius && CollisionsHelper::BoxIntersectsSphere(cascadeBoundsWorld, e.Bounds)) + auto& list = scene->Actors[drawCategory]; + for (const auto& e : list) { - //PROFILE_CPU_ACTOR(e.Actor); - e.Actor->Draw(renderContext); - actorsDrawn++; + if (e.Bounds.Radius >= minObjectRadius && viewMask & e.LayerMask && CollisionsHelper::BoxIntersectsSphere(cascadeBoundsWorld, e.Bounds)) + { + //PROFILE_CPU_ACTOR(e.Actor); + e.Actor->Draw(renderContext); + actorsDrawn++; + } } } } diff --git a/Source/Engine/Renderer/LightPass.cpp b/Source/Engine/Renderer/LightPass.cpp index 3565a60b5..5f0bf9de8 100644 --- a/Source/Engine/Renderer/LightPass.cpp +++ b/Source/Engine/Renderer/LightPass.cpp @@ -239,7 +239,7 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi // Render all point lights for (int32 lightIndex = 0; lightIndex < mainCache->PointLights.Count(); lightIndex++) { - PROFILE_GPU_CPU("Point Light"); + PROFILE_GPU_CPU_NAMED("Point Light"); // Cache data auto& light = mainCache->PointLights[lightIndex]; @@ -296,7 +296,7 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi // Render all spot lights for (int32 lightIndex = 0; lightIndex < mainCache->SpotLights.Count(); lightIndex++) { - PROFILE_GPU_CPU("Spot Light"); + PROFILE_GPU_CPU_NAMED("Spot Light"); // Cache data auto& light = mainCache->SpotLights[lightIndex]; @@ -353,7 +353,7 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi // Render all directional lights for (int32 lightIndex = 0; lightIndex < mainCache->DirectionalLights.Count(); lightIndex++) { - PROFILE_GPU_CPU("Directional Light"); + PROFILE_GPU_CPU_NAMED("Directional Light"); // Cache data auto& light = mainCache->DirectionalLights[lightIndex]; @@ -390,7 +390,7 @@ void LightPass::RenderLight(RenderContextBatch& renderContextBatch, GPUTextureVi // Render all sky lights for (int32 lightIndex = 0; lightIndex < mainCache->SkyLights.Count(); lightIndex++) { - PROFILE_GPU_CPU("Sky Light"); + PROFILE_GPU_CPU_NAMED("Sky Light"); // Cache data auto& light = mainCache->SkyLights[lightIndex]; diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 60b57fa26..49bfc8f22 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -408,7 +408,7 @@ void RenderList::Clear() _instanceBuffer.Clear(); } -void RenderList::AddDrawCall(DrawPass drawModes, StaticFlags staticFlags, DrawCall& drawCall, bool receivesDecals) +void RenderList::AddDrawCall(DrawPass drawModes, StaticFlags staticFlags, const DrawCall& drawCall, bool receivesDecals) { #if ENABLE_ASSERTION_LOW_LAYERS // Ensure that draw modes are non-empty and in conjunction with material draw modes @@ -417,8 +417,7 @@ void RenderList::AddDrawCall(DrawPass drawModes, StaticFlags staticFlags, DrawCa #endif // Append draw call data - const int32 index = DrawCalls.Count(); - DrawCalls.Add(drawCall); + const int32 index = DrawCalls.Add(drawCall); // Add draw call to proper draw lists if (drawModes & DrawPass::Depth) @@ -446,7 +445,7 @@ void RenderList::AddDrawCall(DrawPass drawModes, StaticFlags staticFlags, DrawCa } } -void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawPass drawModes, StaticFlags staticFlags, ShadowsCastingMode shadowsMode, const BoundingSphere& bounds, DrawCall& drawCall, bool receivesDecals) +void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawPass drawModes, StaticFlags staticFlags, ShadowsCastingMode shadowsMode, const BoundingSphere& bounds, const DrawCall& drawCall, bool receivesDecals) { #if ENABLE_ASSERTION_LOW_LAYERS // Ensure that draw modes are non-empty and in conjunction with material draw modes @@ -455,8 +454,7 @@ void RenderList::AddDrawCall(const RenderContextBatch& renderContextBatch, DrawP #endif // Append draw call data - const int32 index = DrawCalls.Count(); - DrawCalls.Add(drawCall); + const int32 index = DrawCalls.Add(drawCall); // Add draw call to proper draw lists const RenderContext& mainRenderContext = renderContextBatch.Contexts.Get()[0]; @@ -524,8 +522,8 @@ namespace void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseDistance, DrawCallsList& list, const DrawCall* drawCalls) { PROFILE_CPU(); - - const int32 listSize = (int32)list.Indices.Count(); + const auto* listData = list.Indices.Get(); + const int32 listSize = list.Indices.Count(); const Float3 planeNormal = renderContext.View.Direction; const float planePoint = -Float3::Dot(planeNormal, renderContext.View.Position); @@ -541,7 +539,7 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD const uint32 sortKeyXor = reverseDistance ? MAX_uint32 : 0; for (int32 i = 0; i < listSize; i++) { - const DrawCall& drawCall = drawCalls[list.Indices[i]]; + const DrawCall& drawCall = drawCalls[listData[i]]; const float distance = Float3::Dot(planeNormal, drawCall.ObjectPosition) - planePoint; const uint32 sortKey = RenderTools::ComputeDistanceSortKey(distance) ^ sortKeyXor; int32 batchKey = GetHash(drawCall.Geometry.IndexBuffer); @@ -571,14 +569,14 @@ void RenderList::SortDrawCalls(const RenderContext& renderContext, bool reverseD list.Batches.Clear(); for (int32 i = 0; i < listSize;) { - const DrawCall& drawCall = drawCalls[list.Indices[i]]; + const DrawCall& drawCall = drawCalls[listData[i]]; int32 batchSize = 1; int32 instanceCount = drawCall.InstanceCount; // Check the following draw calls to merge them (using instancing) for (int32 j = i + 1; j < listSize; j++) { - const DrawCall& other = drawCalls[list.Indices[j]]; + const DrawCall& other = drawCalls[listData[j]]; if (!CanBatchWith(drawCall, other)) break; @@ -610,6 +608,8 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL if (list.IsEmpty()) return; PROFILE_GPU_CPU("Drawing"); + const auto* listData = list.Indices.Get(); + const auto* batchesData = list.Batches.Get(); const auto context = GPUDevice::Instance->GetMainContext(); bool useInstancing = list.CanUseInstancing && CanUseInstancing(renderContext.View.Pass) && GPUDevice::Instance->Limits.HasInstancing; @@ -623,7 +623,7 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL int32 batchesCount = 0; for (int32 i = 0; i < list.Batches.Count(); i++) { - auto& batch = list.Batches[i]; + auto& batch = batchesData[i]; if (batch.BatchSize > 1) batchesCount += batch.BatchSize; } @@ -646,14 +646,14 @@ void RenderList::ExecuteDrawCalls(const RenderContext& renderContext, DrawCallsL // Write to instance buffer for (int32 i = 0; i < list.Batches.Count(); i++) { - auto& batch = list.Batches[i]; + auto& batch = batchesData[i]; if (batch.BatchSize > 1) { IMaterial::InstancingHandler handler; - drawCalls[list.Indices[batch.StartIndex]].Material->CanUseInstancing(handler); + drawCalls[listData[batch.StartIndex]].Material->CanUseInstancing(handler); for (int32 j = 0; j < batch.BatchSize; j++) { - auto& drawCall = drawCalls[list.Indices[batch.StartIndex + j]]; + auto& drawCall = drawCalls[listData[batch.StartIndex + j]]; handler.WriteDrawCall(instanceData, drawCall); instanceData++; } @@ -685,8 +685,8 @@ DRAW: uint32 vbOffsets[4]; for (int32 i = 0; i < list.Batches.Count(); i++) { - auto& batch = list.Batches[i]; - const DrawCall& drawCall = drawCalls[list.Indices[batch.StartIndex]]; + auto& batch = batchesData[i]; + const DrawCall& drawCall = drawCalls[listData[batch.StartIndex]]; int32 vbCount = 0; while (vbCount < ARRAY_COUNT(drawCall.Geometry.VertexBuffers) && drawCall.Geometry.VertexBuffers[vbCount]) @@ -789,11 +789,11 @@ DRAW: bindParams.DrawCallsCount = 1; for (int32 i = 0; i < list.Batches.Count(); i++) { - auto& batch = list.Batches[i]; + auto& batch = batchesData[i]; for (int32 j = 0; j < batch.BatchSize; j++) { - const DrawCall& drawCall = drawCalls[list.Indices[batch.StartIndex + j]]; + const DrawCall& drawCall = drawCalls[listData[batch.StartIndex + j]]; bindParams.FirstDrawCall = &drawCall; drawCall.Material->Bind(bindParams); @@ -815,10 +815,11 @@ DRAW: auto& batch = BatchedDrawCalls[list.PreBatchedDrawCalls[i]]; auto drawCall = batch.DrawCall; bindParams.FirstDrawCall = &drawCall; + const auto* instancesData = batch.Instances.Get(); for (int32 j = 0; j < batch.Instances.Count(); j++) { - auto& instance = batch.Instances[j]; + auto& instance = instancesData[j]; drawCall.ObjectPosition = instance.InstanceOrigin; drawCall.PerInstanceRandom = instance.PerInstanceRandom; auto lightmapArea = instance.InstanceLightmapArea.ToFloat4(); @@ -840,7 +841,7 @@ DRAW: // Draw calls list has nto been batched so execute draw calls separately for (int32 j = 0; j < list.Indices.Count(); j++) { - const DrawCall& drawCall = drawCalls[list.Indices[j]]; + const DrawCall& drawCall = drawCalls[listData[j]]; bindParams.FirstDrawCall = &drawCall; drawCall.Material->Bind(bindParams); diff --git a/Source/Engine/Renderer/RenderList.h b/Source/Engine/Renderer/RenderList.h index a00a318b1..a0f23f2e7 100644 --- a/Source/Engine/Renderer/RenderList.h +++ b/Source/Engine/Renderer/RenderList.h @@ -3,6 +3,7 @@ #pragma once #include "Engine/Core/Collections/Array.h" +#include "Engine/Core/Collections/ConcurrentArray.h" #include "Engine/Core/Math/Half.h" #include "Engine/Graphics/PostProcessSettings.h" #include "Engine/Graphics/DynamicBuffer.h" @@ -236,7 +237,7 @@ struct DrawCallsList /// /// The list of draw calls indices to render. /// - Array Indices; + ConcurrentArray Indices; /// /// The list of external draw calls indices to render. @@ -290,7 +291,7 @@ public: /// /// Draw calls list (for all draw passes). /// - Array DrawCalls; + ConcurrentArray DrawCalls; /// /// Draw calls list with pre-batched instances (for all draw passes). @@ -481,7 +482,7 @@ public: /// The object static flags. /// The draw call data. /// True if the rendered mesh can receive decals. - void AddDrawCall(DrawPass drawModes, StaticFlags staticFlags, DrawCall& drawCall, bool receivesDecals); + void AddDrawCall(DrawPass drawModes, StaticFlags staticFlags, const DrawCall& drawCall, bool receivesDecals); /// /// Adds the draw call to the draw lists and references it in other render contexts. Performs additional per-context frustum culling. @@ -493,7 +494,7 @@ public: /// The object bounds. /// The draw call data. /// True if the rendered mesh can receive decals. - void AddDrawCall(const RenderContextBatch& renderContextBatch, DrawPass drawModes, StaticFlags staticFlags, ShadowsCastingMode shadowsMode, const BoundingSphere& bounds, DrawCall& drawCall, bool receivesDecals); + void AddDrawCall(const RenderContextBatch& renderContextBatch, DrawPass drawModes, StaticFlags staticFlags, ShadowsCastingMode shadowsMode, const BoundingSphere& bounds, const DrawCall& drawCall, bool receivesDecals); /// /// Sorts the collected draw calls list. diff --git a/Source/Engine/Renderer/Renderer.cpp b/Source/Engine/Renderer/Renderer.cpp index 6fb379eeb..e2c3d1bec 100644 --- a/Source/Engine/Renderer/Renderer.cpp +++ b/Source/Engine/Renderer/Renderer.cpp @@ -31,7 +31,9 @@ #include "AntiAliasing/SMAA.h" #include "Engine/Level/Actor.h" #include "Engine/Level/Level.h" +#include "Engine/Level/Scene/SceneRendering.h" #include "Engine/Core/Config/GraphicsSettings.h" +#include "Engine/Threading/JobSystem.h" #if USE_EDITOR #include "Editor/Editor.h" #include "Editor/QuadOverdrawPass.h" @@ -261,6 +263,9 @@ void Renderer::DrawSceneDepth(GPUContext* context, SceneRenderTask* task, GPUTex // Draw scene actors RenderContextBatch renderContextBatch(renderContext); Level::DrawActors(renderContextBatch); + for (const int64 label : renderContextBatch.WaitLabels) + JobSystem::Wait(label); + renderContextBatch.WaitLabels.Clear(); } // Sort draw calls @@ -341,7 +346,16 @@ void RenderInner(SceneRenderTask* task, RenderContext& renderContext, RenderCont if (drawShadows) ShadowsPass::Instance()->SetupShadows(renderContext, renderContextBatch); - task->OnCollectDrawCalls(renderContextBatch); + // Dispatch drawing (via JobSystem - multiple job batches for every scene) + JobSystem::SetJobStartingOnDispatch(false); + task->OnCollectDrawCalls(renderContextBatch, SceneRendering::DrawCategory::SceneDraw); + task->OnCollectDrawCalls(renderContextBatch, SceneRendering::DrawCategory::SceneDrawAsync); + + // Wait for async jobs to finish + JobSystem::SetJobStartingOnDispatch(true); + for (const int64 label : renderContextBatch.WaitLabels) + JobSystem::Wait(label); + renderContextBatch.WaitLabels.Clear(); } // Sort draw calls diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index c297f65ce..70de78ed0 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -369,3 +369,12 @@ void JobSystem::SetJobStartingOnDispatch(bool value) } #endif } + +int32 JobSystem::GetThreadsCount() +{ +#if JOB_SYSTEM_ENABLED + return ThreadsCount; +#else + return 0; +#endif +} diff --git a/Source/Engine/Threading/JobSystem.h b/Source/Engine/Threading/JobSystem.h index e009e2f29..cb88fee37 100644 --- a/Source/Engine/Threading/JobSystem.h +++ b/Source/Engine/Threading/JobSystem.h @@ -41,4 +41,9 @@ API_CLASS(Static) class FLAXENGINE_API JobSystem /// Sets whether automatically start jobs execution on Dispatch. If disabled jobs won't be executed until it gets re-enabled. Can be used to optimize execution of multiple dispatches that should overlap. /// API_FUNCTION() static void SetJobStartingOnDispatch(bool value); + + /// + /// Gets the amount of job system threads. + /// + API_PROPERTY() static int32 GetThreadsCount(); };