diff --git a/Source/Engine/Core/Collections/RingBuffer.h b/Source/Engine/Core/Collections/RingBuffer.h index 898c51c67..4dcc75e33 100644 --- a/Source/Engine/Core/Collections/RingBuffer.h +++ b/Source/Engine/Core/Collections/RingBuffer.h @@ -5,6 +5,7 @@ #include "Engine/Platform/Platform.h" #include "Engine/Core/Memory/Memory.h" #include "Engine/Core/Memory/Allocation.h" +#include "Engine/Core/Math/Math.h" /// /// Template for ring buffer with variable capacity. @@ -98,4 +99,10 @@ public: Memory::DestructItems(Get() + Math::Min(_front, _back), _count); _front = _back = _count = 0; } + + void Release() + { + Clear(); + _allocation.Free(); + } }; diff --git a/Source/Engine/Core/Memory/SimpleHeapAllocation.h b/Source/Engine/Core/Memory/SimpleHeapAllocation.h new file mode 100644 index 000000000..0428d1d7f --- /dev/null +++ b/Source/Engine/Core/Memory/SimpleHeapAllocation.h @@ -0,0 +1,86 @@ +// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. + +#pragma once + +#include "Engine/Core/Memory/Memory.h" +#include "Engine/Core/Types/BaseTypes.h" + +// Base class for custom heap-based allocators (eg. with local pooling/paging). Expects only Allocate/Free methods to be provided. +template +class SimpleHeapAllocation +{ +public: + enum { HasSwap = true }; + + template + class Data + { + T* _data = nullptr; + uintptr _size; + + public: + FORCE_INLINE Data() + { + } + + FORCE_INLINE ~Data() + { + if (_data) + This::Free(_data, _size); + } + + FORCE_INLINE T* Get() + { + return _data; + } + + FORCE_INLINE const T* Get() const + { + return _data; + } + + FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, int32 minCapacity) const + { + capacity = capacity ? capacity * 2 : InitialCapacity; + if (capacity < minCapacity) + capacity = minCapacity; + return capacity; + } + + FORCE_INLINE void Allocate(uint64 capacity) + { + _size = capacity * sizeof(T); + _data = (T*)This::Allocate(_size); + } + + FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount) + { + T* newData = capacity != 0 ? (T*)This::Allocate(capacity * sizeof(T)) : nullptr; + if (oldCount) + { + if (newCount > 0) + Memory::MoveItems(newData, _data, newCount); + Memory::DestructItems(_data, oldCount); + } + if (_data) + This::Free(_data, _size); + _data = newData; + _size = capacity * sizeof(T); + } + + FORCE_INLINE void Free() + { + if (_data) + { + This::Free(_data, _size); + _data = nullptr; + } + } + + FORCE_INLINE void Swap(Data& other) + { + ::Swap(_data, other._data); + ::Swap(_size, other._size); + } + }; +}; diff --git a/Source/Engine/Renderer/RenderList.cpp b/Source/Engine/Renderer/RenderList.cpp index 09e7f9b62..1ca5cf244 100644 --- a/Source/Engine/Renderer/RenderList.cpp +++ b/Source/Engine/Renderer/RenderList.cpp @@ -30,13 +30,7 @@ namespace Array SortingBatches; Array FreeRenderList; - struct MemPoolEntry - { - void* Ptr; - uintptr Size; - }; - - Array MemPool; + Array> MemPool; CriticalSection MemPoolLocker; } @@ -147,18 +141,16 @@ void* RendererAllocation::Allocate(uintptr size) MemPoolLocker.Lock(); for (int32 i = 0; i < MemPool.Count(); i++) { - if (MemPool[i].Size == size) + if (MemPool.Get()[i].Second == size) { - result = MemPool[i].Ptr; + result = MemPool.Get()[i].First; MemPool.RemoveAt(i); break; } } MemPoolLocker.Unlock(); if (!result) - { result = Platform::Allocate(size, 16); - } return result; } @@ -201,7 +193,7 @@ void RenderList::CleanupCache() SortingIndices.Resize(0); FreeRenderList.ClearDelete(); for (auto& e : MemPool) - Platform::Free(e.Ptr); + Platform::Free(e.First); MemPool.Clear(); } diff --git a/Source/Engine/Renderer/RendererAllocation.h b/Source/Engine/Renderer/RendererAllocation.h index 42cd5e755..c0ef46a91 100644 --- a/Source/Engine/Renderer/RendererAllocation.h +++ b/Source/Engine/Renderer/RendererAllocation.h @@ -2,86 +2,11 @@ #pragma once -#include "Engine/Core/Memory/Memory.h" -#include "Engine/Core/Types/BaseTypes.h" +#include "Engine/Core/Memory/SimpleHeapAllocation.h" -class RendererAllocation +class RendererAllocation : public SimpleHeapAllocation { public: static FLAXENGINE_API void* Allocate(uintptr size); static FLAXENGINE_API void Free(void* ptr, uintptr size); - - enum { HasSwap = true }; - - template - class Data - { - T* _data = nullptr; - uintptr _size; - - public: - FORCE_INLINE Data() - { - } - - FORCE_INLINE ~Data() - { - if (_data) - RendererAllocation::Free(_data, _size); - } - - FORCE_INLINE T* Get() - { - return _data; - } - - FORCE_INLINE const T* Get() const - { - return _data; - } - - FORCE_INLINE int32 CalculateCapacityGrow(int32 capacity, int32 minCapacity) const - { - capacity = capacity ? capacity * 2 : 64; - if (capacity < minCapacity) - capacity = minCapacity; - return capacity; - } - - FORCE_INLINE void Allocate(uint64 capacity) - { - _size = capacity * sizeof(T); - _data = (T*)RendererAllocation::Allocate(_size); - } - - FORCE_INLINE void Relocate(uint64 capacity, int32 oldCount, int32 newCount) - { - T* newData = capacity != 0 ? (T*)RendererAllocation::Allocate(capacity * sizeof(T)) : nullptr; - if (oldCount) - { - if (newCount > 0) - Memory::MoveItems(newData, _data, newCount); - Memory::DestructItems(_data, oldCount); - } - if (_data) - RendererAllocation::Free(_data, _size); - _data = newData; - _size = capacity * sizeof(T); - } - - FORCE_INLINE void Free() - { - if (_data) - { - RendererAllocation::Free(_data, _size); - _data = nullptr; - } - } - - FORCE_INLINE void Swap(Data& other) - { - ::Swap(_data, other._data); - ::Swap(_size, other._size); - } - }; }; diff --git a/Source/Engine/Threading/JobSystem.cpp b/Source/Engine/Threading/JobSystem.cpp index 34e2b4b9a..847291bbc 100644 --- a/Source/Engine/Threading/JobSystem.cpp +++ b/Source/Engine/Threading/JobSystem.cpp @@ -6,6 +6,8 @@ #include "Engine/Platform/Thread.h" #include "Engine/Platform/ConditionVariable.h" #include "Engine/Core/Types/Span.h" +#include "Engine/Core/Types/Pair.h" +#include "Engine/Core/Memory/SimpleHeapAllocation.h" #include "Engine/Core/Collections/Dictionary.h" #include "Engine/Core/Collections/RingBuffer.h" #include "Engine/Engine/EngineService.h" @@ -18,6 +20,14 @@ #if JOB_SYSTEM_ENABLED +// Local allocator for job system memory that uses internal pooling and assumes that JobsLocker is taken (write access owned by the calling thread). +class JobSystemAllocation : public SimpleHeapAllocation +{ +public: + static void* Allocate(uintptr size); + static void Free(void* ptr, uintptr size); +}; + class JobSystemService : public EngineService { public: @@ -46,9 +56,9 @@ struct TIsPODType struct JobContext { volatile int64 JobsLeft; - volatile int64 DependenciesLeft; + int32 DependenciesLeft; Function Job; - Array Dependants; + Array Dependants; }; template<> @@ -80,12 +90,13 @@ public: namespace { JobSystemService JobSystemInstance; + Array> MemPool; Thread* Threads[PLATFORM_THREADS_LIMIT / 2] = {}; int32 ThreadsCount = 0; bool JobStartingOnDispatch = true; volatile int64 ExitFlag = 0; volatile int64 JobLabel = 0; - Dictionary JobContexts; + Dictionary JobContexts; ConditionVariable JobsSignal; CriticalSection JobsMutex; ConditionVariable WaitSignal; @@ -94,6 +105,28 @@ namespace RingBuffer Jobs; } +void* JobSystemAllocation::Allocate(uintptr size) +{ + void* result = nullptr; + for (int32 i = 0; i < MemPool.Count(); i++) + { + if (MemPool.Get()[i].Second == size) + { + result = MemPool.Get()[i].First; + MemPool.RemoveAt(i); + break; + } + } + if (!result) + result = Platform::Allocate(size, 16); + return result; +} + +void JobSystemAllocation::Free(void* ptr, uintptr size) +{ + MemPool.Add({ ptr, size }); +} + bool JobSystemService::Init() { ThreadsCount = Math::Min(Platform::GetCPUInfo().LogicalProcessorCount, ARRAY_COUNT(Threads)); @@ -130,6 +163,12 @@ void JobSystemService::Dispose() Threads[i] = nullptr; } } + + JobContexts.SetCapacity(0); + Jobs.Release(); + for (auto& e : MemPool) + Platform::Free(e.First); + MemPool.Clear(); } int32 JobSystemThread::Run() @@ -176,7 +215,7 @@ int32 JobSystemThread::Run() for (int64 dependant : context.Dependants) { JobContext& dependantContext = JobContexts.At(dependant); - if (Platform::InterlockedDecrement(&dependantContext.DependenciesLeft) <= 0) + if (--dependantContext.DependenciesLeft <= 0) { // Dispatch dependency when it's ready JobData dependantData; @@ -245,7 +284,7 @@ int64 JobSystem::Dispatch(const Function& job, int32 jobCount) context.DependenciesLeft = 0; JobsLocker.Lock(); - JobContexts.Add(label, context); + JobContexts.Add(label, MoveTemp(context)); for (data.Index = 0; data.Index < jobCount; data.Index++) Jobs.PushBack(data); JobsLocker.Unlock(); @@ -291,9 +330,10 @@ int64 JobSystem::Dispatch(const Function& job, Span dependen dependencyContext->Dependants.Add(label); } } - JobContexts.Add(label, context); + JobContexts.Add(label, MoveTemp(context)); if (context.DependenciesLeft == 0) { + // No dependencies left to complete so dispatch now for (data.Index = 0; data.Index < jobCount; data.Index++) Jobs.PushBack(data); }