Browse Source

Refactoring memory pools a bit

Panagiotis Christopoulos Charitos 11 years ago
parent
commit
c13065ac99
6 changed files with 810 additions and 986 deletions
  1. 58 14
      include/anki/util/Allocator.h
  2. 217 119
      include/anki/util/Memory.h
  3. 402 828
      src/util/Memory.cpp
  4. 64 0
      tests/framework/Framework.h
  5. 20 3
      tests/util/Allocator.cpp
  6. 49 22
      tests/util/Memory.cpp

+ 58 - 14
include/anki/util/Allocator.h

@@ -68,11 +68,11 @@ public:
 	};
 	};
 
 
 	/// Default constructor
 	/// Default constructor
-	GenericPoolAllocator() noexcept
+	GenericPoolAllocator()
 	{}
 	{}
 
 
 	/// Copy constructor
 	/// Copy constructor
-	GenericPoolAllocator(const GenericPoolAllocator& b) noexcept
+	GenericPoolAllocator(const GenericPoolAllocator& b)
 	{
 	{
 		*this = b;
 		*this = b;
 	}
 	}
@@ -80,34 +80,47 @@ public:
 	/// Copy constructor
 	/// Copy constructor
 	template<typename Y>
 	template<typename Y>
 	GenericPoolAllocator(const GenericPoolAllocator<
 	GenericPoolAllocator(const GenericPoolAllocator<
-		Y, TPool, checkFree>& b) noexcept
+		Y, TPool, checkFree>& b)
 	{
 	{
 		*this = b;
 		*this = b;
 	}
 	}
 
 
-	/// Constuctor that accepts a pool
+	/// Constuctor that creates a pool
 	template<typename... TArgs>
 	template<typename... TArgs>
 	explicit GenericPoolAllocator(
 	explicit GenericPoolAllocator(
 		AllocAlignedCallback allocCb, void* allocCbUserData,
 		AllocAlignedCallback allocCb, void* allocCbUserData,
-		TArgs&&... args) noexcept
+		TArgs&&... args)
 	{
 	{
-		Error error = m_pool.create(
+		m_pool = reinterpret_cast<TPool*>(
+			allocCb(allocCbUserData, nullptr, sizeof(TPool), alignof(TPool)));
+		if(!m_pool)
+		{
+			ANKI_LOGF("Initialization failed");
+		}
+
+		new (m_pool) TPool();
+
+		Error error = m_pool->create(
 			allocCb, allocCbUserData, std::forward<TArgs>(args)...);
 			allocCb, allocCbUserData, std::forward<TArgs>(args)...);
 
 
 		if(error)
 		if(error)
 		{
 		{
 			ANKI_LOGF("Initialization failed");
 			ANKI_LOGF("Initialization failed");
 		}
 		}
+
+		m_pool->getRefcount() = 1;
 	}
 	}
 
 
 	/// Destructor
 	/// Destructor
 	~GenericPoolAllocator()
 	~GenericPoolAllocator()
-	{}
+	{
+		clear();
+	}
 
 
 	/// Copy
 	/// Copy
 	GenericPoolAllocator& operator=(const GenericPoolAllocator& b)
 	GenericPoolAllocator& operator=(const GenericPoolAllocator& b)
 	{
 	{
-		m_pool = b.m_pool;
+		copy(b);
 		return *this;
 		return *this;
 	}
 	}
 
 
@@ -116,7 +129,7 @@ public:
 	GenericPoolAllocator& operator=(const GenericPoolAllocator<
 	GenericPoolAllocator& operator=(const GenericPoolAllocator<
 		U, TPool, checkFree>& b)
 		U, TPool, checkFree>& b)
 	{
 	{
-		m_pool = b.m_pool;
+		copy(b);
 		return *this;
 		return *this;
 	}
 	}
 
 
@@ -148,7 +161,7 @@ public:
 			? *reinterpret_cast<const PtrSize*>(hint) 
 			? *reinterpret_cast<const PtrSize*>(hint) 
 			: alignof(value_type);
 			: alignof(value_type);
 
 
-		void* out = m_pool.allocate(size, alignment);
+		void* out = m_pool->allocate(size, alignment);
 
 
 		if(out == nullptr)
 		if(out == nullptr)
 		{
 		{
@@ -163,7 +176,7 @@ public:
 	{
 	{
 		(void)n;
 		(void)n;
 
 
-		Bool ok = m_pool.free(p);
+		Bool ok = m_pool->free(p);
 
 
 		if(checkFree && !ok)
 		if(checkFree && !ok)
 		{
 		{
@@ -211,14 +224,16 @@ public:
 	/// @note This is AnKi specific
 	/// @note This is AnKi specific
 	const TPool& getMemoryPool() const
 	const TPool& getMemoryPool() const
 	{
 	{
-		return m_pool;
+		ANKI_ASSERT(m_pool);
+		return *m_pool;
 	}
 	}
 
 
 	/// Get the memory pool
 	/// Get the memory pool
 	/// @note This is AnKi specific
 	/// @note This is AnKi specific
 	TPool& getMemoryPool()
 	TPool& getMemoryPool()
 	{
 	{
-		return m_pool;
+		ANKI_ASSERT(m_pool);
+		return *m_pool;
 	}
 	}
 
 
 	/// Allocate a new object and call it's constructor
 	/// Allocate a new object and call it's constructor
@@ -315,7 +330,36 @@ public:
 	}
 	}
 
 
 private:
 private:
-	TPool m_pool;
+	TPool* m_pool = nullptr;
+
+	template<typename Y>
+	void copy(const GenericPoolAllocator<Y, TPool, checkFree>& b)
+	{
+		clear();
+		if(b.m_pool)
+		{
+			m_pool = b.m_pool;
+			++m_pool->getRefcount();
+		}
+	}
+
+	void clear()
+	{
+		if(m_pool)
+		{
+			auto count = --m_pool->getRefcount();
+			if(count == 0)
+			{
+				auto allocCb = m_pool->getAllocationCallback();
+				auto ud = m_pool->getAllocationCallbackUserData();
+				ANKI_ASSERT(allocCb);
+				m_pool->~TPool();
+				allocCb(ud, m_pool, 0, 0);
+			}
+
+			m_pool = nullptr;
+		}
+	}
 };
 };
 
 
 /// @name GenericPoolAllocator global functions
 /// @name GenericPoolAllocator global functions

+ 217 - 119
include/anki/util/Memory.h

@@ -7,12 +7,19 @@
 #define ANKI_UTIL_MEMORY_H
 #define ANKI_UTIL_MEMORY_H
 
 
 #include "anki/util/StdTypes.h"
 #include "anki/util/StdTypes.h"
+#include "anki/util/NonCopyable.h"
+#include "anki/util/Atomic.h"
 
 
 namespace anki {
 namespace anki {
 
 
+// Forward
+class SpinLock;
+
 /// @addtogroup util_memory
 /// @addtogroup util_memory
 /// @{
 /// @{
 
 
+#define ANKI_MEM_USE_SIGNATURES ANKI_DEBUG
+
 /// Allocate aligned memory
 /// Allocate aligned memory
 void* mallocAligned(PtrSize size, PtrSize alignmentBytes);
 void* mallocAligned(PtrSize size, PtrSize alignmentBytes);
 
 
@@ -23,6 +30,9 @@ void freeAligned(void* ptr);
 /// See allocAligned function for the explanation of arguments
 /// See allocAligned function for the explanation of arguments
 using AllocAlignedCallback = void* (*)(void*, void*, PtrSize, PtrSize);
 using AllocAlignedCallback = void* (*)(void*, void*, PtrSize, PtrSize);
 
 
+/// An internal type.
+using AllocationSignature = U32;
+
 /// This is a function that allocates and deallocates heap memory. 
 /// This is a function that allocates and deallocates heap memory. 
 /// If the @a ptr is nullptr then it allocates using the @a size and 
 /// If the @a ptr is nullptr then it allocates using the @a size and 
 /// @a alignment. If the @a ptr is not nullptr it deallocates the memory and
 /// @a alignment. If the @a ptr is not nullptr it deallocates the memory and
@@ -48,63 +58,68 @@ public:
 	///       and secondly, the default constructors of the allocators that use
 	///       and secondly, the default constructors of the allocators that use
 	///       that pool will call that constructor and that happens a lot.
 	///       that pool will call that constructor and that happens a lot.
 	///       If that constructor does some actual job then we have a problem.
 	///       If that constructor does some actual job then we have a problem.
-	HeapMemoryPool()
-	:	m_impl(nullptr)
-	{}
-
-	/// Copy constructor. It's not copying any data
-	HeapMemoryPool(const HeapMemoryPool& other)
-	:	m_impl(nullptr)
-	{
-		*this = other;
-	}
+	HeapMemoryPool();
 
 
 	/// Destroy
 	/// Destroy
-	~HeapMemoryPool()
-	{
-		clear();
-	}
+	~HeapMemoryPool();
 
 
-	/// Copy. It will not copy any data, what it will do is add visibility of
-	/// other's pool to this instance as well
-	HeapMemoryPool& operator=(const HeapMemoryPool& other);
-
-	/// The real constructor
+	/// The real constructor.
 	/// @param allocCb The allocation function callback
 	/// @param allocCb The allocation function callback
 	/// @param allocCbUserData The user data to pass to the allocation function
 	/// @param allocCbUserData The user data to pass to the allocation function
 	Error create(AllocAlignedCallback allocCb, void* allocCbUserData);
 	Error create(AllocAlignedCallback allocCb, void* allocCbUserData);
 
 
-	/// Check if two memory pools are the same one.
-	Bool operator==(const HeapMemoryPool& b) const
-	{
-		return m_impl == b.m_impl;
-	}
-
 	/// Allocate memory
 	/// Allocate memory
 	void* allocate(PtrSize size, PtrSize alignment);
 	void* allocate(PtrSize size, PtrSize alignment);
 
 
 	/// Free memory
 	/// Free memory
 	Bool free(void* ptr);
 	Bool free(void* ptr);
 
 
+	/// Get refcount.
+	AtomicU32& getRefcount()
+	{
+		return m_refcount;
+	}
+
+	/// Get the number of users for this pool.
+	U32 getUsersCount() const
+	{
+		return m_refcount.load();
+	}
+
 	/// Return number of allocations
 	/// Return number of allocations
-	U32 getAllocationsCount() const;
+	U32 getAllocationsCount() const
+	{
+		return m_allocationsCount.load();
+	}
 
 
-private:
-	// Forward. Hide the implementation because Memory.h is the base of other
-	// files and should not include them
-	class Implementation;
+	/// Get allocation callback.
+	AllocAlignedCallback getAllocationCallback() const
+	{
+		return m_allocCb;
+	}
 
 
-	/// The actual implementation
-	Implementation* m_impl;
+	/// Get allocation callback user data.
+	void* getAllocationCallbackUserData() const
+	{
+		return m_allocCbUserData;
+	}
 
 
-	/// Clear the pool
-	void clear();
+private:
+	AtomicU32 m_refcount = {0};
+	AllocAlignedCallback m_allocCb = nullptr;
+	void* m_allocCbUserData = nullptr;
+	AtomicU32 m_allocationsCount = {0};
+#if ANKI_MEM_USE_SIGNATURES
+	AllocationSignature m_signature = 0;
+	static const U32 MAX_ALIGNMENT = 16;
+	U32 m_headerSize = 0;
+#endif
 };
 };
 
 
 /// Thread safe memory pool. It's a preallocated memory pool that is used for 
 /// Thread safe memory pool. It's a preallocated memory pool that is used for 
 /// memory allocations on top of that preallocated memory. It is mainly used by 
 /// memory allocations on top of that preallocated memory. It is mainly used by 
 /// fast stack allocators
 /// fast stack allocators
-class StackMemoryPool
+class StackMemoryPool: public NonCopyable
 {
 {
 	friend class ChainMemoryPool;
 	friend class ChainMemoryPool;
 
 
@@ -113,41 +128,19 @@ public:
 	using Snapshot = void*;
 	using Snapshot = void*;
 
 
 	/// Default constructor
 	/// Default constructor
-	StackMemoryPool()
-	:	m_impl(nullptr)
-	{}
-
-	/// Copy constructor. It's not copying any data
-	StackMemoryPool(const StackMemoryPool& other)
-	:	m_impl(nullptr)
-	{
-		*this = other;
-	}
+	StackMemoryPool();
 
 
 	/// Destroy
 	/// Destroy
-	~StackMemoryPool()
-	{
-		clear();
-	}
-
-	/// Copy. It will not copy any data, what it will do is add visibility of
-	/// other's pool to this instance as well
-	StackMemoryPool& operator=(const StackMemoryPool& other);
-
-	/// Check if two memory pools are the same one.
-	Bool operator==(const StackMemoryPool& b) const
-	{
-		return m_impl == b.m_impl;
-	}
+	~StackMemoryPool();
 
 
 	/// Create with parameters
 	/// Create with parameters
-	/// @param alloc The allocation function callback
-	/// @param allocUserData The user data to pass to the allocation function
+	/// @param allocCb The allocation function callback
+	/// @param allocCbUserData The user data to pass to the allocation function
 	/// @param size The size of the pool
 	/// @param size The size of the pool
 	/// @param alignmentBytes The maximum supported alignment for returned
 	/// @param alignmentBytes The maximum supported alignment for returned
 	///                       memory
 	///                       memory
 	Error create(
 	Error create(
-		AllocAlignedCallback alloc, void* allocUserData,
+		AllocAlignedCallback allocCb, void* allocCbUserData,
 		PtrSize size, PtrSize alignmentBytes = ANKI_SAFE_ALIGNMENT);
 		PtrSize size, PtrSize alignmentBytes = ANKI_SAFE_ALIGNMENT);
 
 
 	/// Allocate aligned memory. The operation is thread safe
 	/// Allocate aligned memory. The operation is thread safe
@@ -163,17 +156,47 @@ public:
 	/// @return True if the deallocation actually happened and false otherwise
 	/// @return True if the deallocation actually happened and false otherwise
 	Bool free(void* ptr);
 	Bool free(void* ptr);
 
 
-	/// Reinit the pool. All existing allocated memory will be lost
-	void reset();
+	/// Get the refcount.
+	AtomicU32& getRefcount()
+	{
+		return m_refcount;
+	}
+
+	/// Get the number of users for this pool.
+	U32 getUsersCount() const
+	{
+		return m_refcount.load();
+	}
 
 
-	/// Get the total size
-	PtrSize getTotalSize() const;
+	/// Get allocation callback.
+	AllocAlignedCallback getAllocationCallback() const
+	{
+		return m_allocCb;
+	}
 
 
-	/// Get the allocated size
-	PtrSize getAllocatedSize() const;
+	/// Get allocation callback user data.
+	void* getAllocationCallbackUserData() const
+	{
+		return m_allocCbUserData;
+	}
 
 
-	/// Get the number of users for this pool
-	U32 getUsersCount() const;
+	/// Return number of allocations
+	U32 getAllocationsCount() const
+	{
+		return m_allocationsCount.load();
+	}
+
+	/// Get the total size.
+	PtrSize getTotalSize() const
+	{
+		return m_memsize;
+	}
+
+	/// Get the allocated size.
+	PtrSize getAllocatedSize() const
+	{
+		return m_top.load() - m_memory;
+	}
 
 
 	/// Get a snapshot of the current state that can be used to reset the 
 	/// Get a snapshot of the current state that can be used to reset the 
 	/// pool's state later on. Not recommended on multithreading scenarios
 	/// pool's state later on. Not recommended on multithreading scenarios
@@ -185,24 +208,52 @@ public:
 	/// @param s The snapshot to be used
 	/// @param s The snapshot to be used
 	void resetUsingSnapshot(Snapshot s);
 	void resetUsingSnapshot(Snapshot s);
 
 
-	/// Return number of allocations
-	U32 getAllocationsCount() const;
+	/// Reinit the pool. All existing allocated memory will be lost
+	void reset();
 
 
 private:
 private:
-	// Forward. Hide the implementation because Memory.h is the base of other
-	// files and should not include them
-	class Implementation;
+	/// The header of each allocation
+	struct MemoryBlockHeader
+	{
+		U8 m_size[sizeof(U32)]; ///< It's U8 to allow whatever alignment
+	};
+
+	static_assert(alignof(MemoryBlockHeader) == 1, "Alignment error");
+	static_assert(sizeof(MemoryBlockHeader) == sizeof(U32), "Size error");
+
+	/// Refcount
+	AtomicU32 m_refcount = {0};
+
+	/// User allocation function
+	AllocAlignedCallback m_allocCb = nullptr;
+
+	/// User allocation function data
+	void* m_allocCbUserData = nullptr;
+
+	/// Alignment of allocations
+	PtrSize m_alignmentBytes = 0;
+
+	/// Aligned size of MemoryBlockHeader
+	PtrSize m_headerSize = 0;
+
+	/// Pre-allocated memory chunk
+	U8* m_memory = nullptr;
 
 
-	/// The actual implementation
-	Implementation* m_impl;
+	/// Size of the pre-allocated memory chunk
+	PtrSize m_memsize = 0;
 
 
-	/// Clear the pool
-	void clear();
+	/// Points to the memory and more specifically to the top of the stack
+	Atomic<U8*> m_top = {nullptr};
+
+	AtomicU32 m_allocationsCount = {0};
+
+	/// The ChainMemoryPool will set that to true.
+	Bool8 m_ignoreAllocationErrors = false;
 };
 };
 
 
 /// Chain memory pool. Almost similar to StackMemoryPool but more flexible and 
 /// Chain memory pool. Almost similar to StackMemoryPool but more flexible and 
 /// at the same time a bit slower.
 /// at the same time a bit slower.
-class ChainMemoryPool
+class ChainMemoryPool: public NonCopyable
 {
 {
 public:
 public:
 	/// Chunk allocation method. Defines the size a newely created chunk should
 	/// Chunk allocation method. Defines the size a newely created chunk should
@@ -210,42 +261,21 @@ public:
 	/// allocations
 	/// allocations
 	enum class ChunkGrowMethod: U8
 	enum class ChunkGrowMethod: U8
 	{
 	{
+		NONE,
 		FIXED, ///< All chunks have the same size
 		FIXED, ///< All chunks have the same size
 		MULTIPLY, ///< Next chuck's size will be old_chuck_size * a_value
 		MULTIPLY, ///< Next chuck's size will be old_chuck_size * a_value
 		ADD ///< Next chuck's size will be old_chuck_size + a_value
 		ADD ///< Next chuck's size will be old_chuck_size + a_value
 	};
 	};
 
 
 	/// Default constructor
 	/// Default constructor
-	ChainMemoryPool()
-	:	m_impl(nullptr)
-	{}
-
-	/// Copy constructor. It's not copying any data
-	ChainMemoryPool(const ChainMemoryPool& other)
-	:	m_impl(nullptr)
-	{
-		*this = other;
-	}
+	ChainMemoryPool();
 
 
 	/// Destroy
 	/// Destroy
-	~ChainMemoryPool()
-	{
-		clear();
-	}
-
-	/// Copy. It will not copy any data, what it will do is add visibility of
-	/// other's pool to this instance as well
-	ChainMemoryPool& operator=(const ChainMemoryPool& other);
-
-	/// Check if two memory pools are the same one.
-	Bool operator==(const ChainMemoryPool& b) const
-	{
-		return m_impl == b.m_impl;
-	}
+	~ChainMemoryPool();
 
 
 	/// Constructor with parameters
 	/// Constructor with parameters
-	/// @param alloc The allocation function callback
-	/// @param allocUserData The user data to pass to the allocation function
+	/// @param allocCb The allocation function callback
+	/// @param allocCbUserData The user data to pass to the allocation function
 	/// @param initialChunkSize The size of the first chunk
 	/// @param initialChunkSize The size of the first chunk
 	/// @param maxChunkSize The size of the chunks cannot exceed that number
 	/// @param maxChunkSize The size of the chunks cannot exceed that number
 	/// @param chunkAllocStepMethod How new chunks grow compared to the old ones
 	/// @param chunkAllocStepMethod How new chunks grow compared to the old ones
@@ -254,11 +284,11 @@ public:
 	/// @param alignmentBytes The maximum supported alignment for returned
 	/// @param alignmentBytes The maximum supported alignment for returned
 	///                       memory
 	///                       memory
 	Error create(
 	Error create(
-		AllocAlignedCallback alloc, void* allocUserData,
+		AllocAlignedCallback allocCb, 
+		void* allocCbUserData,
 		PtrSize initialChunkSize,
 		PtrSize initialChunkSize,
 		PtrSize maxChunkSize,
 		PtrSize maxChunkSize,
-		ChunkGrowMethod chunkAllocStepMethod = 
-			ChunkGrowMethod::MULTIPLY, 
+		ChunkGrowMethod chunkAllocStepMethod = ChunkGrowMethod::MULTIPLY, 
 		PtrSize chunkAllocStep = 2, 
 		PtrSize chunkAllocStep = 2, 
 		PtrSize alignmentBytes = ANKI_SAFE_ALIGNMENT);
 		PtrSize alignmentBytes = ANKI_SAFE_ALIGNMENT);
 
 
@@ -274,13 +304,37 @@ public:
 	/// @return True if the deallocation actually happened and false otherwise
 	/// @return True if the deallocation actually happened and false otherwise
 	Bool free(void* ptr);
 	Bool free(void* ptr);
 
 
+	/// Get refcount.
+	AtomicU32& getRefcount()
+	{
+		return m_refcount;
+	}
+
 	/// Get the number of users for this pool
 	/// Get the number of users for this pool
-	U32 getUsersCount() const;
+	U32 getUsersCount() const
+	{
+		return m_refcount.load();
+	}
+
+	/// Get allocation callback.
+	AllocAlignedCallback getAllocationCallback() const
+	{
+		return m_allocCb;
+	}
+
+	/// Get allocation callback user data.
+	void* getAllocationCallbackUserData() const
+	{
+		return m_allocCbUserData;
+	}
 
 
 	/// Return number of allocations
 	/// Return number of allocations
-	U32 getAllocationsCount() const;
+	U32 getAllocationsCount() const
+	{
+		return m_allocationsCount;
+	}
 
 
-	/// @name Methods used for optimizing future chains
+	/// @name Methods used for optimizing future chains.
 	/// @{
 	/// @{
 	PtrSize getChunksCount() const;
 	PtrSize getChunksCount() const;
 
 
@@ -288,15 +342,59 @@ public:
 	/// @}
 	/// @}
 
 
 private:
 private:
-	// Forward. Hide the implementation because Memory.h is the base of other
-	// files and should not include them
-	class Implementation;
+	/// A chunk of memory
+	struct Chunk
+	{
+		StackMemoryPool m_pool;
+
+		/// Used to identify if the chunk can be deleted
+		U32 m_allocationsCount = 0;
+
+		/// Next chunk in the list
+		Chunk* m_next = nullptr;
+	};
+
+	/// Refcount
+	AtomicU32 m_refcount = {0};
+
+	/// User allocation function
+	AllocAlignedCallback m_allocCb = nullptr;
+
+	/// User allocation function data
+	void* m_allocCbUserData = nullptr;
+
+	/// Alignment of allocations
+	PtrSize m_alignmentBytes = 0;
+
+	/// The first chunk
+	Chunk* m_headChunk = nullptr;
+
+	/// Current chunk to allocate from
+	Chunk* m_tailChunk = nullptr;
+
+	/// Fast thread locking
+	SpinLock* m_lock = nullptr;
+
+	/// Chunk first chunk size
+	PtrSize m_initSize = 0;
+
+	/// Chunk max size
+	PtrSize m_maxSize = 0;
+
+	/// Chunk allocation method value
+	U32 m_step = 0;
+
+	/// Chunk allocation method
+	ChunkGrowMethod m_method = ChunkGrowMethod::NONE;
+
+	/// Allocations number.
+	U32 m_allocationsCount = 0;
 
 
-	/// The actual implementation
-	Implementation* m_impl;
+	/// Create a new chunk
+	Chunk* createNewChunk(PtrSize size);
 
 
-	/// Clear the pool
-	void clear();
+	/// Allocate from chunk
+	void* allocateFromChunk(Chunk* ch, PtrSize size, PtrSize alignment);
 };
 };
 
 
 /// @}
 /// @}

+ 402 - 828
src/util/Memory.cpp

@@ -138,166 +138,86 @@ void* allocAligned(
 //==============================================================================
 //==============================================================================
 
 
 //==============================================================================
 //==============================================================================
-/// The hidden implementation of HeapMemoryPool
-class HeapMemoryPool::Implementation: public NonCopyable
+HeapMemoryPool::HeapMemoryPool()
+{}
+
+//==============================================================================
+HeapMemoryPool::~HeapMemoryPool()
 {
 {
-public:
-	AtomicU32 m_refcount;
-	AtomicU32 m_allocationsCount;
-	AllocAlignedCallback m_allocCb;
-	void* m_allocCbUserData;
-#if ANKI_MEM_SIGNATURES
-	Signature m_signature;
-	static const U32 MAX_ALIGNMENT = 16;
-	U32 m_headerSize;
-#endif
+	ANKI_ASSERT(m_refcount == 0 && "Refcount should be zero");
 
 
-	~Implementation()
+	if(m_allocationsCount != 0)
 	{
 	{
-		if(m_allocationsCount != 0)
-		{
-			ANKI_LOGW("Memory pool destroyed before all memory being released");
-		}
+		ANKI_LOGW("Memory pool destroyed before all memory being released");
 	}
 	}
+}
 
 
-	void create(AllocAlignedCallback allocCb, void* allocCbUserData)
-	{
-		m_refcount = 1;
-		m_allocationsCount = 0;
-		m_allocCb = allocCb;
-		m_allocCbUserData = allocCbUserData;
-#if ANKI_MEM_SIGNATURES
-		m_signature = computeSignature(this);
-		m_headerSize = getAlignedRoundUp(MAX_ALIGNMENT, sizeof(Signature));
-#endif
-	}
+//==============================================================================
+Error HeapMemoryPool::create(
+	AllocAlignedCallback allocCb, void* allocCbUserData)
+{
+	ANKI_ASSERT(m_allocCb == nullptr);
+	ANKI_ASSERT(allocCb != nullptr);
 
 
-	void* allocate(PtrSize size, PtrSize alignment)
-	{
+	m_refcount = 0;
+	m_allocationsCount = 0;
+	m_allocCb = allocCb;
+	m_allocCbUserData = allocCbUserData;
 #if ANKI_MEM_SIGNATURES
 #if ANKI_MEM_SIGNATURES
-		ANKI_ASSERT(alignment <= MAX_ALIGNMENT && "Wrong assumption");
-		size += m_headerSize;
+	m_signature = computeSignature(this);
+	m_headerSize = getAlignedRoundUp(MAX_ALIGNMENT, sizeof(Signature));
 #endif
 #endif
 
 
-		void* mem = m_allocCb(m_allocCbUserData, nullptr, size, alignment);
-
-		if(mem != nullptr)
-		{
-			++m_allocationsCount;
+	return ErrorCode::NONE;
+}
 
 
+//==============================================================================
+void* HeapMemoryPool::allocate(PtrSize size, PtrSize alignment)
+{	
 #if ANKI_MEM_SIGNATURES
 #if ANKI_MEM_SIGNATURES
-			memset(mem, 0, m_headerSize);
-			memcpy(mem, &m_signature, sizeof(m_signature));
-			U8* memU8 = static_cast<U8*>(mem);
-			memU8 += m_headerSize;
-			mem = static_cast<void*>(memU8);
+	ANKI_ASSERT(alignment <= MAX_ALIGNMENT && "Wrong assumption");
+	size += m_headerSize;
 #endif
 #endif
-		}
-		else
-		{
-			ANKI_LOGE("Out of memory");
-		}
 
 
-		return mem;
-	}
+	void* mem = m_allocCb(m_allocCbUserData, nullptr, size, alignment);
 
 
-	Bool free(void* ptr)
+	if(mem != nullptr)
 	{
 	{
-#if ANKI_MEM_SIGNATURES
-		U8* memU8 = static_cast<U8*>(ptr);
-		memU8 -= m_headerSize;
-		if(memcmp(memU8, &m_signature, sizeof(m_signature)) != 0)
-		{
-			ANKI_LOGE("Signature missmatch on free");
-		}
+		++m_allocationsCount;
 
 
-		ptr = static_cast<void*>(memU8);
+#if ANKI_MEM_SIGNATURES
+		memset(mem, 0, m_headerSize);
+		memcpy(mem, &m_signature, sizeof(m_signature));
+		U8* memU8 = static_cast<U8*>(mem);
+		memU8 += m_headerSize;
+		mem = static_cast<void*>(memU8);
 #endif
 #endif
-		--m_allocationsCount;
-		m_allocCb(m_allocCbUserData, ptr, 0, 0);
-
-		return true;
-	}
-};
-
-//==============================================================================
-HeapMemoryPool& HeapMemoryPool::operator=(const HeapMemoryPool& other)
-{
-	clear();
-
-	if(other.m_impl)
-	{
-		m_impl = other.m_impl;
-		++m_impl->m_refcount;
-	}
-
-	return *this;
-}
-
-//==============================================================================
-Error HeapMemoryPool::create(
-	AllocAlignedCallback allocCb, void* allocCbUserData)
-{
-	ANKI_ASSERT(allocCb != nullptr);
-
-	Error error = ErrorCode::NONE;
-	m_impl = static_cast<Implementation*>(allocCb(allocCbUserData, nullptr, 
-		sizeof(Implementation), alignof(Implementation)));
-
-	if(m_impl)
-	{
-		m_impl->create(allocCb, allocCbUserData);
 	}
 	}
 	else
 	else
 	{
 	{
 		ANKI_LOGE("Out of memory");
 		ANKI_LOGE("Out of memory");
-		error = ErrorCode::OUT_OF_MEMORY;
 	}
 	}
 
 
-	return error;
+	return mem;
 }
 }
 
 
 //==============================================================================
 //==============================================================================
-void HeapMemoryPool::clear()
+Bool HeapMemoryPool::free(void* ptr)
 {
 {
-	if(m_impl)
+#if ANKI_MEM_SIGNATURES
+	U8* memU8 = static_cast<U8*>(ptr);
+	memU8 -= m_headerSize;
+	if(memcmp(memU8, &m_signature, sizeof(m_signature)) != 0)
 	{
 	{
-		U32 refcount = --m_impl->m_refcount;
-
-		if(refcount == 0)
-		{
-			auto allocCb = m_impl->m_allocCb;
-			auto ud = m_impl->m_allocCbUserData;
-			ANKI_ASSERT(allocCb);
-
-			m_impl->~Implementation();
-			allocCb(ud, m_impl, 0, 0);
-		}
-
-		m_impl = nullptr;
+		ANKI_LOGE("Signature missmatch on free");
 	}
 	}
-}
 
 
-//==============================================================================
-void* HeapMemoryPool::allocate(PtrSize size, PtrSize alignment)
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->allocate(size, alignment);
-}
-
-//==============================================================================
-Bool HeapMemoryPool::free(void* ptr)
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->free(ptr);
-}
-
-//==============================================================================
-U32 HeapMemoryPool::getAllocationsCount() const
-{
-	ANKI_ASSERT(m_impl != nullptr);
+	ptr = static_cast<void*>(memU8);
+#endif
+	--m_allocationsCount;
+	m_allocCb(m_allocCbUserData, ptr, 0, 0);
 
 
-	return m_impl->m_allocationsCount.load();
+	return true;
 }
 }
 
 
 //==============================================================================
 //==============================================================================
@@ -305,352 +225,188 @@ U32 HeapMemoryPool::getAllocationsCount() const
 //==============================================================================
 //==============================================================================
 
 
 //==============================================================================
 //==============================================================================
-/// The hidden implementation of StackMemoryPool
-class StackMemoryPool::Implementation: public NonCopyable
-{
-public:
-	/// The header of each allocation
-	class MemoryBlockHeader
-	{
-	public:
-		U8 m_size[sizeof(U32)]; ///< It's U8 to allow whatever alignment
-	};
-
-	static_assert(alignof(MemoryBlockHeader) == 1, "Alignment error");
-	static_assert(sizeof(MemoryBlockHeader) == sizeof(U32), "Size error");
-
-	/// Refcount
-	AtomicU32 m_refcount;
+StackMemoryPool::StackMemoryPool()
+{}
 
 
-	/// User allocation function
-	AllocAlignedCallback m_allocCb;
-
-	/// User allocation function data
-	void* m_allocCbUserData;
-
-	/// Alignment of allocations
-	PtrSize m_alignmentBytes;
-
-	/// Aligned size of MemoryBlockHeader
-	PtrSize m_headerSize;
-
-	/// Pre-allocated memory chunk
-	U8* m_memory;
-
-	/// Size of the pre-allocated memory chunk
-	PtrSize m_memsize;
-
-	/// Points to the memory and more specifically to the top of the stack
-	Atomic<U8*> m_top;
-
-	AtomicU32 m_allocationsCount;
-
-	// Destroy
-	~Implementation()
+//==============================================================================
+StackMemoryPool::~StackMemoryPool()
+{
+	if(m_memory != nullptr)
 	{
 	{
-		if(m_memory != nullptr)
-		{
 #if ANKI_DEBUG
 #if ANKI_DEBUG
-			// Invalidate the memory
-			memset(m_memory, 0xCC, m_memsize);
+		// Invalidate the memory
+		memset(m_memory, 0xCC, m_memsize);
 #endif
 #endif
-			m_allocCb(m_allocCbUserData, m_memory, 0, 0);
-		}
+		m_allocCb(m_allocCbUserData, m_memory, 0, 0);
 	}
 	}
+}
 
 
-	// Construct
-	Error create(AllocAlignedCallback allocCb, void* allocCbUserData,
-		PtrSize size, PtrSize alignmentBytes)
-	{
-		ANKI_ASSERT(allocCb);
-		ANKI_ASSERT(size > 0);
-		ANKI_ASSERT(alignmentBytes > 0);
-
-		Error error = ErrorCode::NONE;
-
-		m_refcount = 1;
-		m_allocCb = allocCb;
-		m_allocCbUserData = allocCbUserData;
-		m_alignmentBytes = alignmentBytes;
-		m_memsize = getAlignedRoundUp(alignmentBytes, size);
-		m_allocationsCount = 0;
-
-		m_memory = (U8*)m_allocCb(
-			m_allocCbUserData, nullptr, m_memsize, m_alignmentBytes);
-
-		if(m_memory != nullptr)
-		{
-#if ANKI_DEBUG
-			// Invalidate the memory
-			memset(m_memory, 0xCC, m_memsize);
-#endif
-
-			// Align allocated memory
-			m_top = m_memory;
-
-			// Calc header size
-			m_headerSize = 
-				getAlignedRoundUp(m_alignmentBytes, sizeof(MemoryBlockHeader));
-		}
-		else
-		{
-			ANKI_LOGE("Out of memory");
-			error = ErrorCode::OUT_OF_MEMORY;
-		}
-
-		return error;
-	}
+//==============================================================================
+Error StackMemoryPool::create(
+	AllocAlignedCallback allocCb, void* allocCbUserData,
+	PtrSize size, PtrSize alignmentBytes)
+{
+	ANKI_ASSERT(allocCb);
+	ANKI_ASSERT(size > 0);
+	ANKI_ASSERT(alignmentBytes > 0);
 
 
-	PtrSize getTotalSize() const
-	{
-		return m_memsize;
-	}
+	Error error = ErrorCode::NONE;
 
 
-	PtrSize getAllocatedSize() const
-	{
-		ANKI_ASSERT(m_memory != nullptr);
-		return m_top.load() - m_memory;
-	}
+	m_refcount = 0;
+	m_allocCb = allocCb;
+	m_allocCbUserData = allocCbUserData;
+	m_alignmentBytes = alignmentBytes;
+	m_memsize = getAlignedRoundUp(alignmentBytes, size);
+	m_allocationsCount = 0;
 
 
-	const void* getBaseAddress() const
-	{
-		ANKI_ASSERT(m_memory != nullptr);
-		return m_memory;
-	}
+	m_memory = reinterpret_cast<U8*>(m_allocCb(
+		m_allocCbUserData, nullptr, m_memsize, m_alignmentBytes));
 
 
-	/// Allocate
-	void* allocate(PtrSize size, PtrSize alignment)
+	if(m_memory != nullptr)
 	{
 	{
-		ANKI_ASSERT(m_memory != nullptr);
-		ANKI_ASSERT(alignment <= m_alignmentBytes);
-		(void)alignment;
-
-		size = getAlignedRoundUp(m_alignmentBytes, size + m_headerSize);
-
-		ANKI_ASSERT(size < MAX_U32 && "Too big allocation");
-
-		U8* out = m_top.fetch_add(size);
-
-		if(out + size <= m_memory + m_memsize)
-		{
 #if ANKI_DEBUG
 #if ANKI_DEBUG
-			// Invalidate the block
-			memset(out, 0xCC, size);
+		// Invalidate the memory
+		memset(m_memory, 0xCC, m_memsize);
 #endif
 #endif
 
 
-			// Write the block header
-			MemoryBlockHeader* header = 
-				reinterpret_cast<MemoryBlockHeader*>(out);
-			U32 size32 = size;
-			memcpy(&header->m_size[0], &size32, sizeof(U32));
-
-			// Set the correct output
-			out += m_headerSize;
-
-			// Check alignment
-			ANKI_ASSERT(isAligned(m_alignmentBytes, out));
-
-			// Increase count
-			++m_allocationsCount;
-		}
-		else
-		{
-			// Not always an error
-			out = nullptr;
-		}
+		// Align allocated memory
+		m_top = m_memory;
 
 
-		return out;
+		// Calc header size
+		m_headerSize = 
+			getAlignedRoundUp(m_alignmentBytes, sizeof(MemoryBlockHeader));
 	}
 	}
-
-	/// Free
-	Bool free(void* ptr)
+	else
 	{
 	{
-		// ptr shouldn't be null or not aligned. If not aligned it was not 
-		// allocated by this class
-		ANKI_ASSERT(ptr != nullptr && isAligned(m_alignmentBytes, ptr));
-
-		// memory is nullptr if moved
-		ANKI_ASSERT(m_memory != nullptr);
-
-		// Correct the p
-		U8* realptr = (U8*)ptr - m_headerSize;
-
-		// realptr should be inside the pool's preallocated memory
-		ANKI_ASSERT(realptr >= m_memory);
-
-		// Get block size
-		MemoryBlockHeader* header = (MemoryBlockHeader*)realptr;
-		U32 size;
-		memcpy(&size, &header->m_size[0], sizeof(U32));
+		ANKI_LOGE("Out of memory");
+		error = ErrorCode::OUT_OF_MEMORY;
+	}
 
 
-		// Check if the size is within limits
-		ANKI_ASSERT(realptr + size <= m_memory + m_memsize);
+	return error;
+}
 
 
-		// Atomic stuff
-		U8* expected = realptr + size;
-		U8* desired = realptr;
+//==============================================================================
+void* StackMemoryPool::allocate(PtrSize size, PtrSize alignment)
+{
+	ANKI_ASSERT(m_memory != nullptr);
+	ANKI_ASSERT(alignment <= m_alignmentBytes);
+	(void)alignment;
 
 
-		// if(top == expected) {
-		//     top = desired;
-		//     exchange = true;
-		// } else {
-		//     expected = top;
-		//     exchange = false;
-		// }
-		Bool exchange = m_top.compare_exchange_strong(expected, desired);
+	size = getAlignedRoundUp(m_alignmentBytes, size + m_headerSize);
 
 
-		// Decrease count
-		--m_allocationsCount;
+	ANKI_ASSERT(size < MAX_U32 && "Too big allocation");
 
 
-		return exchange;
-	}
+	U8* out = m_top.fetch_add(size);
 
 
-	/// Reset
-	void reset()
+	if(out + size <= m_memory + m_memsize)
 	{
 	{
-		// memory is nullptr if moved
-		ANKI_ASSERT(m_memory != nullptr);
-
 #if ANKI_DEBUG
 #if ANKI_DEBUG
-		// Invalidate the memory
-		memset(m_memory, 0xCC, m_memsize);
+		// Invalidate the block
+		memset(out, 0xCC, size);
 #endif
 #endif
 
 
-		m_top = m_memory;
-	}
-};
+		// Write the block header
+		MemoryBlockHeader* header = 
+			reinterpret_cast<MemoryBlockHeader*>(out);
+		U32 size32 = size;
+		memcpy(&header->m_size[0], &size32, sizeof(U32));
 
 
-//==============================================================================
-StackMemoryPool& StackMemoryPool::operator=(const StackMemoryPool& other)
-{
-	clear();
+		// Set the correct output
+		out += m_headerSize;
 
 
-	if(other.m_impl)
-	{
-		m_impl = other.m_impl;
-		++m_impl->m_refcount;
-	}
-
-	return *this;
-}
-
-//==============================================================================
-Error StackMemoryPool::create(
-	AllocAlignedCallback alloc, void* allocUserData,
-	PtrSize size, PtrSize alignmentBytes)
-{
-	ANKI_ASSERT(m_impl == nullptr);
+		// Check alignment
+		ANKI_ASSERT(isAligned(m_alignmentBytes, out));
 
 
-	Error error = ErrorCode::NONE;
-	m_impl = static_cast<Implementation*>(alloc(allocUserData, nullptr, 
-		sizeof(Implementation), alignof(Implementation)));
-	
-	if(m_impl)
-	{
-		construct(m_impl);
-		error = m_impl->create(alloc, allocUserData, size, alignmentBytes);
+		// Increase count
+		++m_allocationsCount;
 	}
 	}
 	else
 	else
 	{
 	{
-		ANKI_LOGE("Out of memory");
-		error = ErrorCode::OUT_OF_MEMORY;
+		// Not always an error
+		if(!m_ignoreAllocationErrors)
+		{
+			ANKI_LOGE("Out of memory");
+		}
+
+		out = nullptr;
 	}
 	}
 
 
-	return error;
+	return out;
 }
 }
 
 
 //==============================================================================
 //==============================================================================
-void StackMemoryPool::clear()
+Bool StackMemoryPool::free(void* ptr)
 {
 {
-	if(m_impl)
-	{
-		U32 refcount = --m_impl->m_refcount;
+	// ptr shouldn't be null or not aligned. If not aligned it was not 
+	// allocated by this class
+	ANKI_ASSERT(ptr != nullptr && isAligned(m_alignmentBytes, ptr));
 
 
-		if(refcount == 0)
-		{
-			auto allocCb = m_impl->m_allocCb;
-			auto ud = m_impl->m_allocCbUserData;
-			ANKI_ASSERT(allocCb);
+	// memory is nullptr if moved
+	ANKI_ASSERT(m_memory != nullptr);
 
 
-			m_impl->~Implementation();
-			allocCb(ud, m_impl, 0, 0);
-		}
+	// Correct the p
+	U8* realptr = reinterpret_cast<U8*>(ptr) - m_headerSize;
 
 
-		m_impl = nullptr;
-	}
-}
+	// realptr should be inside the pool's preallocated memory
+	ANKI_ASSERT(realptr >= m_memory);
 
 
-//==============================================================================
-PtrSize StackMemoryPool::getTotalSize() const
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->getTotalSize();
-}
+	// Get block size
+	MemoryBlockHeader* header = reinterpret_cast<MemoryBlockHeader*>(realptr);
+	U32 size;
+	memcpy(&size, &header->m_size[0], sizeof(U32));
 
 
-//==============================================================================
-PtrSize StackMemoryPool::getAllocatedSize() const
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->getAllocatedSize();
-}
+	// Check if the size is within limits
+	ANKI_ASSERT(realptr + size <= m_memory + m_memsize);
 
 
-//==============================================================================
-void* StackMemoryPool::allocate(PtrSize size, PtrSize alignment)
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	void* mem = m_impl->allocate(size, alignment);
+	// Atomic stuff
+	U8* expected = realptr + size;
+	U8* desired = realptr;
 
 
-	if (mem == nullptr)
-	{
-		ANKI_LOGE("Out of memory");
-	}
+	// if(top == expected) {
+	//     top = desired;
+	//     exchange = true;
+	// } else {
+	//     expected = top;
+	//     exchange = false;
+	// }
+	Bool exchange = m_top.compare_exchange_strong(expected, desired);
 
 
-	return mem;
-}
+	// Decrease count
+	--m_allocationsCount;
 
 
-//==============================================================================
-Bool StackMemoryPool::free(void* ptr)
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->free(ptr);
+	return exchange;
 }
 }
 
 
 //==============================================================================
 //==============================================================================
 void StackMemoryPool::reset()
 void StackMemoryPool::reset()
 {
 {
-	ANKI_ASSERT(m_impl != nullptr);
-	m_impl->reset();
-}
+	// memory is nullptr if moved
+	ANKI_ASSERT(m_memory != nullptr);
 
 
-//==============================================================================
-U32 StackMemoryPool::getUsersCount() const
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->m_refcount.load();
+#if ANKI_DEBUG
+	// Invalidate the memory
+	memset(m_memory, 0xCC, m_memsize);
+#endif
+
+	m_top = m_memory;
+	m_allocationsCount = 0;
 }
 }
 
 
 //==============================================================================
 //==============================================================================
 StackMemoryPool::Snapshot StackMemoryPool::getShapshot() const
 StackMemoryPool::Snapshot StackMemoryPool::getShapshot() const
 {
 {
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->m_top.load();
+	ANKI_ASSERT(m_memory != nullptr);
+	return m_top.load();
 }
 }
 
 
 //==============================================================================
 //==============================================================================
 void StackMemoryPool::resetUsingSnapshot(Snapshot s)
 void StackMemoryPool::resetUsingSnapshot(Snapshot s)
 {
 {
-	ANKI_ASSERT(m_impl != nullptr);
-	ANKI_ASSERT(static_cast<U8*>(s) >= m_impl->m_memory);
-	ANKI_ASSERT(static_cast<U8*>(s) < m_impl->m_memory + m_impl->m_memsize);
-
-	m_impl->m_top.store(static_cast<U8*>(s));
-}
+	ANKI_ASSERT(m_memory != nullptr);
+	ANKI_ASSERT(static_cast<U8*>(s) >= m_memory);
+	ANKI_ASSERT(static_cast<U8*>(s) < m_memory + m_memsize);
 
 
-//==============================================================================
-U32 StackMemoryPool::getAllocationsCount() const
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->m_allocationsCount.load();
+	m_top.store(static_cast<U8*>(s));
 }
 }
 
 
 //==============================================================================
 //==============================================================================
@@ -658,514 +414,332 @@ U32 StackMemoryPool::getAllocationsCount() const
 //==============================================================================
 //==============================================================================
 
 
 //==============================================================================
 //==============================================================================
-/// The hidden implementation of ChainMemoryPool
-class ChainMemoryPool::Implementation: public NonCopyable
+ChainMemoryPool::ChainMemoryPool()
+{}
+
+//==============================================================================
+ChainMemoryPool::~ChainMemoryPool()
 {
 {
-public:
-	/// A chunk of memory
-	class Chunk
+	Chunk* ch = m_headChunk;
+	while(ch)
 	{
 	{
-	public:
-		StackMemoryPool::Implementation m_pool;
-
-		/// Used to identify if the chunk can be deleted
-		U32 m_allocationsCount = 0;
-
-		/// Next chunk in the list
-		Chunk* m_next = nullptr;
-	};
-
-	/// Refcount
-	AtomicU32 m_refcount = {1};
-
-	/// User allocation function
-	AllocAlignedCallback m_allocCb;
-
-	/// User allocation function data
-	void* m_allocCbUserData;
-
-	/// Alignment of allocations
-	PtrSize m_alignmentBytes;
-
-	/// The first chunk
-	Chunk* m_headChunk = nullptr;
-
-	/// Current chunk to allocate from
-	Chunk* m_tailChunk = nullptr;
-
-	/// Fast thread locking
-	SpinLock m_lock;
-
-	/// Chunk first chunk size
-	PtrSize m_initSize;
-
-	/// Chunk max size
-	PtrSize m_maxSize;
-
-	/// Chunk allocation method value
-	U32 m_step;
+		Chunk* next = ch->m_next;
 
 
-	/// Chunk allocation method
-	ChainMemoryPool::ChunkGrowMethod m_method;
+		ch->~Chunk();
+		m_allocCb(m_allocCbUserData, ch, 0, 0);
 
 
-	/// Allocations number.
-	AtomicU32 m_allocationsCount = {0};
+		ch = next;
+	}
 
 
-	/// Construct
-	Implementation(
-		AllocAlignedCallback allocCb, 
-		void* allocCbUserData,
-		PtrSize initialChunkSize,
-		PtrSize maxChunkSize,
-		ChainMemoryPool::ChunkGrowMethod chunkAllocStepMethod, 
-		PtrSize chunkAllocStep, 
-		PtrSize alignmentBytes)
-	:	m_allocCb(allocCb),
-		m_allocCbUserData(allocCbUserData),
-		m_alignmentBytes(alignmentBytes), 
-		m_initSize(initialChunkSize),
-		m_maxSize(maxChunkSize),
-		m_step((U32)chunkAllocStep),
-		m_method(chunkAllocStepMethod)
+	if(m_lock)
 	{
 	{
 		ANKI_ASSERT(m_allocCb);
 		ANKI_ASSERT(m_allocCb);
-
-		// Initial size should be > 0
-		ANKI_ASSERT(m_initSize > 0 && "Wrong arg");
-
-		// On fixed step should be 0
-		if(m_method == ChainMemoryPool::ChunkGrowMethod::FIXED)
-		{
-			ANKI_ASSERT(m_step == 0 && "Wrong arg");
-		}
-
-		// On fixed initial size is the same as the max
-		if(m_method == ChainMemoryPool::ChunkGrowMethod::FIXED)
-		{
-			ANKI_ASSERT(m_initSize == m_maxSize && "Wrong arg");
-		}
-
-		// On add and mul the max size should be greater than initial
-		if(m_method == ChainMemoryPool::ChunkGrowMethod::ADD 
-			|| m_method == ChainMemoryPool::ChunkGrowMethod::MULTIPLY)
-		{
-			ANKI_ASSERT(m_initSize < m_maxSize && "Wrong arg");
-		}
+		m_lock->~SpinLock();
+		m_allocCb(m_allocCbUserData, m_lock, 0, 0);
 	}
 	}
+}
 
 
-	/// Destroy
-	~Implementation()
+//==============================================================================
+Error ChainMemoryPool::create(
+	AllocAlignedCallback allocCb, 
+	void* allocCbUserData,
+	PtrSize initialChunkSize,
+	PtrSize maxChunkSize,
+	ChunkGrowMethod chunkAllocStepMethod, 
+	PtrSize chunkAllocStep, 
+	PtrSize alignmentBytes)
+{
+	ANKI_ASSERT(m_allocCb == nullptr);
+
+	// Set all values
+	m_allocCb = allocCb;
+	m_allocCbUserData = allocCbUserData;
+	m_alignmentBytes = alignmentBytes;
+	m_initSize = initialChunkSize;
+	m_maxSize = maxChunkSize;
+	m_step = chunkAllocStep;
+	m_method = chunkAllocStepMethod;
+	
+	m_lock = reinterpret_cast<SpinLock*>(m_allocCb(
+		m_allocCbUserData, nullptr, sizeof(SpinLock), alignof(SpinLock)));
+	if(!m_lock)
 	{
 	{
-		Chunk* ch = m_headChunk;
-		while(ch)
-		{
-			Chunk* next = ch->m_next;
+		return ErrorCode::OUT_OF_MEMORY;
+	}
+	construct(m_lock);
 
 
-			ch->~Chunk();
-			m_allocCb(m_allocCbUserData, ch, 0, 0);
+	// Initial size should be > 0
+	ANKI_ASSERT(m_initSize > 0 && "Wrong arg");
 
 
-			ch = next;
-		}
+	// On fixed step should be 0
+	if(m_method == ChainMemoryPool::ChunkGrowMethod::FIXED)
+	{
+		ANKI_ASSERT(m_step == 0 && "Wrong arg");
 	}
 	}
 
 
-#if 0
-	PtrSize computeNewChunkSize(PtrSize allocationSize)
+	// On fixed initial size is the same as the max
+	if(m_method == ChainMemoryPool::ChunkGrowMethod::FIXED)
 	{
 	{
-		PtrSize crntMaxSize;
-		if(m_method == ChainMemoryPool::ChunkGrowMethod::FIXED)
-		{
-			crntMaxSize = m_initSize;
-		}
-		else
-		{
-			// Get the size of the previous max chunk
-			if(m_tailChunk != nullptr)
-			{
-				// Get the size of previous
-				crntMaxSize = m_tailChunk->m_pool.getTotalSize();
-
-				// Increase it
-				if(m_method == ChainMemoryPool::ChunkGrowMethod::MULTIPLY)
-				{
-					crntMaxSize *= m_step;
-				}
-				else
-				{
-					ANKI_ASSERT(m_method 
-						== ChainMemoryPool::ChunkGrowMethod::ADD);
-					crntMaxSize += m_step;
-				}
-			}
-			else
-			{
-				// No chunks. Choose initial size
-
-				ANKI_ASSERT(m_headChunk == nullptr);
-				crntMaxSize = m_initSize;
-			}
-
-			ANKI_ASSERT(crntMaxSize > 0);
-
-			// Fix the size
-			crntMaxSize = std::min(crntMaxSize, (PtrSize)m_maxSize);
-		}
-
-		size = std::max(crntMaxSize, size) + 16;
+		ANKI_ASSERT(m_initSize == m_maxSize && "Wrong arg");
 	}
 	}
-#endif
 
 
-	/// Create a new chunk
-	Chunk* createNewChunk(PtrSize size)
+	// On add and mul the max size should be greater than initial
+	if(m_method == ChainMemoryPool::ChunkGrowMethod::ADD 
+		|| m_method == ChainMemoryPool::ChunkGrowMethod::MULTIPLY)
 	{
 	{
-		//
-		// Calculate preferred size
-		//
-		
-		// Get the size of the next chunk
-		PtrSize crntMaxSize;
-		if(m_method == ChainMemoryPool::ChunkGrowMethod::FIXED)
-		{
-			crntMaxSize = m_initSize;
-		}
-		else
-		{
-			// Get the size of the previous max chunk
-			if(m_tailChunk != nullptr)
-			{
-				// Get the size of previous
-				crntMaxSize = m_tailChunk->m_pool.getTotalSize();
-
-				// Increase it
-				if(m_method == ChainMemoryPool::ChunkGrowMethod::MULTIPLY)
-				{
-					crntMaxSize *= m_step;
-				}
-				else
-				{
-					ANKI_ASSERT(m_method 
-						== ChainMemoryPool::ChunkGrowMethod::ADD);
-					crntMaxSize += m_step;
-				}
-			}
-			else
-			{
-				// No chunks. Choose initial size
-
-				ANKI_ASSERT(m_headChunk == nullptr);
-				crntMaxSize = m_initSize;
-			}
+		ANKI_ASSERT(m_initSize < m_maxSize && "Wrong arg");
+	}
 
 
-			ANKI_ASSERT(crntMaxSize > 0);
+	return ErrorCode::NONE;
+}
 
 
-			// Fix the size
-			crntMaxSize = min(crntMaxSize, (PtrSize)m_maxSize);
-		}
+//==============================================================================
+void* ChainMemoryPool::allocate(PtrSize size, PtrSize alignment)
+{
+	ANKI_ASSERT(m_allocCb);
+	ANKI_ASSERT(size <= m_maxSize);
 
 
-		size = max(crntMaxSize, size) + 16;
+	Chunk* ch;
+	void* mem = nullptr;
 
 
-		//
-		// Create the chunk
-		//
-		Chunk* chunk = (Chunk*)m_allocCb(
-			m_allocCbUserData, nullptr, sizeof(Chunk), alignof(Chunk));
+	LockGuard<SpinLock> lock(*m_lock);
 
 
-		if(chunk)
-		{
-			// Construct it
-			construct(chunk);
-			Error error = chunk->m_pool.create(
-				m_allocCb, m_allocCbUserData, size, m_alignmentBytes);
-
-			if(!error)
-			{
-				// Register it
-				if(m_tailChunk)
-				{
-					m_tailChunk->m_next = chunk;
-					m_tailChunk = chunk;
-				}
-				else
-				{
-					ANKI_ASSERT(m_headChunk == nullptr);
-
-					m_headChunk = m_tailChunk = chunk;
-				}
-			}
-			else
-			{
-				destruct(chunk);
-				m_allocCb(m_allocCbUserData, chunk, 0, 0);
-				chunk = nullptr;
-			}
-		}
-		else
-		{
-			ANKI_LOGE("Out of memory");
-		}
-		
-		return chunk;
-	}
+	// Get chunk
+	ch = m_tailChunk;
 
 
-	/// Allocate from chunk
-	void* allocateFromChunk(Chunk* ch, PtrSize size, PtrSize alignment)
+	// Create new chunk if needed
+	if(ch == nullptr 
+		|| (mem = allocateFromChunk(ch, size, alignment)) == nullptr)
 	{
 	{
-		ANKI_ASSERT(ch);
-		ANKI_ASSERT(size <= m_maxSize);
-		void* mem = ch->m_pool.allocate(size, alignment);
+		// Create new chunk
+		ch = createNewChunk(size);
 
 
-		if(mem)
-		{
-			++ch->m_allocationsCount;
-		}
-		else
+		// Chunk creation failed
+		if(ch == nullptr)
 		{
 		{
-			// Chunk is full. Need a new one
+			return mem;
 		}
 		}
-
-		return mem;
 	}
 	}
 
 
-	/// Allocate memory
-	void* allocate(PtrSize size, PtrSize alignment)
+	if(mem == nullptr)
 	{
 	{
-		ANKI_ASSERT(size <= m_maxSize);
-
-		Chunk* ch;
-		void* mem = nullptr;
+		mem = allocateFromChunk(ch, size, alignment);
+		ANKI_ASSERT(mem != nullptr && "The chunk should have space");
+	}
 
 
-		m_lock.lock();
+	++m_allocationsCount;
 
 
-		// Get chunk
-		ch = m_tailChunk;
+	return mem;
+}
 
 
-		// Create new chunk if needed
-		if(ch == nullptr 
-			|| (mem = allocateFromChunk(ch, size, alignment)) == nullptr)
-		{
-			// Create new chunk
-			ch = createNewChunk(size);
+//==============================================================================
+Bool ChainMemoryPool::free(void* ptr)
+{
+	ANKI_ASSERT(m_allocCb);
+	if(ptr == nullptr)
+	{
+		return true;
+	}
 
 
-			// Chunk creation failed
-			if(ch == nullptr)
-			{
-				m_lock.unlock();
-				return mem;
-			}
-		}
+	LockGuard<SpinLock> lock(*m_lock);
 
 
-		if(mem == nullptr)
+	// Get the chunk that ptr belongs to
+	Chunk* chunk = m_headChunk;
+	Chunk* prevChunk = nullptr;
+	while(chunk)
+	{
+		const U8* from = chunk->m_pool.m_memory;
+		const U8* to = from + chunk->m_pool.m_memsize;
+		const U8* cptr = reinterpret_cast<const U8*>(ptr);
+		if(cptr >= from && cptr < to)
 		{
 		{
-			mem = allocateFromChunk(ch, size, alignment);
-			ANKI_ASSERT(mem != nullptr && "The chunk should have space");
+			break;
 		}
 		}
 
 
-		m_lock.unlock();
-		++m_allocationsCount;
-
-		return mem;
+		prevChunk = chunk;
+		chunk = chunk->m_next;
 	}
 	}
 
 
-	/// Free memory
-	Bool free(void* ptr)
-	{
-		if(ptr == nullptr)
-		{
-			return true;
-		}
+	ANKI_ASSERT(chunk != nullptr 
+		&& "Not initialized or ptr is incorrect");
 
 
-		m_lock.lock();
+	// Decrease the deallocation refcount and if it's zero delete the chunk
+	ANKI_ASSERT(chunk->m_allocationsCount > 0);
+	if(--chunk->m_allocationsCount == 0)
+	{
+		// Chunk is empty. Delete it
 
 
-		// Get the chunk that ptr belongs to
-		Chunk* chunk = m_headChunk;
-		Chunk* prevChunk = nullptr;
-		while(chunk)
+		if(prevChunk != nullptr)
 		{
 		{
-			const U8* from = (const U8*)chunk->m_pool.getBaseAddress();
-			const U8* to = from + chunk->m_pool.getTotalSize();
-			const U8* cptr = (const U8*)ptr;
-			if(cptr >= from && cptr < to)
-			{
-				break;
-			}
-
-			prevChunk = chunk;
-			chunk = chunk->m_next;
+			ANKI_ASSERT(m_headChunk != chunk);
+			prevChunk->m_next = chunk->m_next;
 		}
 		}
 
 
-		ANKI_ASSERT(chunk != nullptr 
-			&& "Not initialized or ptr is incorrect");
-
-		// Decrease the deallocation refcount and if it's zero delete the chunk
-		ANKI_ASSERT(chunk->m_allocationsCount > 0);
-		if(--chunk->m_allocationsCount == 0)
+		if(chunk == m_headChunk)
 		{
 		{
-			// Chunk is empty. Delete it
-
-			if(prevChunk != nullptr)
-			{
-				ANKI_ASSERT(m_headChunk != chunk);
-				prevChunk->m_next = chunk->m_next;
-			}
-
-			if(chunk == m_headChunk)
-			{
-				ANKI_ASSERT(prevChunk == nullptr);
-				m_headChunk = chunk->m_next;
-			}
-
-			if(chunk == m_tailChunk)
-			{
-				m_tailChunk = prevChunk;
-			}
-
-			// Finaly delete it
-			chunk->~Chunk();
-			m_allocCb(m_allocCbUserData, chunk, 0, 0);
+			ANKI_ASSERT(prevChunk == nullptr);
+			m_headChunk = chunk->m_next;
 		}
 		}
 
 
-		m_lock.unlock();
-
-		--m_allocationsCount;
-
-		return true;
-	}
-
-	PtrSize getAllocatedSize() const
-	{
-		PtrSize sum = 0;
-		Chunk* ch = m_headChunk;
-		while(ch)
+		if(chunk == m_tailChunk)
 		{
 		{
-			sum += ch->m_pool.getAllocatedSize();
-			ch = ch->m_next;
+			m_tailChunk = prevChunk;
 		}
 		}
 
 
-		return sum;
+		// Finaly delete it
+		chunk->~Chunk();
+		m_allocCb(m_allocCbUserData, chunk, 0, 0);
 	}
 	}
 
 
-	PtrSize getChunksCount() const
-	{
-		PtrSize count = 0;
-		Chunk* ch = m_headChunk;
-		while(ch)
-		{
-			++count;
-			ch = ch->m_next;
-		}
+	--m_allocationsCount;
 
 
-		return count;
-	}
-};
+	return true;
+}
 
 
 //==============================================================================
 //==============================================================================
-ChainMemoryPool& ChainMemoryPool::operator=(const ChainMemoryPool& other)
+PtrSize ChainMemoryPool::getChunksCount() const
 {
 {
-	clear();
+	ANKI_ASSERT(m_allocCb);
 
 
-	if(other.m_impl)
+	PtrSize count = 0;
+	Chunk* ch = m_headChunk;
+	while(ch)
 	{
 	{
-		m_impl = other.m_impl;
-		++m_impl->m_refcount;
+		++count;
+		ch = ch->m_next;
 	}
 	}
 
 
-	return *this;
+	return count;
 }
 }
 
 
 //==============================================================================
 //==============================================================================
-Error ChainMemoryPool::create(
-	AllocAlignedCallback alloc, 
-	void* allocUserData,
-	PtrSize initialChunkSize,
-	PtrSize maxChunkSize,
-	ChunkGrowMethod chunkAllocStepMethod, 
-	PtrSize chunkAllocStep, 
-	PtrSize alignmentBytes)
+PtrSize ChainMemoryPool::getAllocatedSize() const
 {
 {
-	ANKI_ASSERT(m_impl == nullptr);
-
-	Error error = ErrorCode::NONE;
-	m_impl = static_cast<Implementation*>(alloc(allocUserData, nullptr, 
-		sizeof(Implementation), alignof(Implementation)));
+	ANKI_ASSERT(m_allocCb);
 
 
-	if(m_impl)
+	PtrSize sum = 0;
+	Chunk* ch = m_headChunk;
+	while(ch)
 	{
 	{
-		construct(
-			m_impl,
-			alloc, allocUserData,
-			initialChunkSize, maxChunkSize, chunkAllocStepMethod, 
-			chunkAllocStep,
-			alignmentBytes);
-	}
-	else
-	{
-		ANKI_LOGE("Out of memory");
-		error = ErrorCode::OUT_OF_MEMORY;
+		sum += ch->m_pool.getAllocatedSize();
+		ch = ch->m_next;
 	}
 	}
 
 
-	return error;
+	return sum;
 }
 }
 
 
 //==============================================================================
 //==============================================================================
-void ChainMemoryPool::clear()
+ChainMemoryPool::Chunk* ChainMemoryPool::createNewChunk(PtrSize size)
 {
 {
-	if(m_impl)
+	//
+	// Calculate preferred size
+	//
+	
+	// Get the size of the next chunk
+	PtrSize crntMaxSize;
+	if(m_method == ChainMemoryPool::ChunkGrowMethod::FIXED)
 	{
 	{
-		U32 refcount = --m_impl->m_refcount;
+		crntMaxSize = m_initSize;
+	}
+	else
+	{
+		// Get the size of the previous max chunk
+		if(m_tailChunk != nullptr)
+		{
+			// Get the size of previous
+			crntMaxSize = m_tailChunk->m_pool.getTotalSize();
 
 
-		if(refcount == 0)
+			// Increase it
+			if(m_method == ChainMemoryPool::ChunkGrowMethod::MULTIPLY)
+			{
+				crntMaxSize *= m_step;
+			}
+			else
+			{
+				ANKI_ASSERT(m_method 
+					== ChainMemoryPool::ChunkGrowMethod::ADD);
+				crntMaxSize += m_step;
+			}
+		}
+		else
 		{
 		{
-			auto allocCb = m_impl->m_allocCb;
-			auto ud = m_impl->m_allocCbUserData;
-			ANKI_ASSERT(allocCb);
+			// No chunks. Choose initial size
 
 
-			m_impl->~Implementation();
-			allocCb(ud, m_impl, 0, 0);
+			ANKI_ASSERT(m_headChunk == nullptr);
+			crntMaxSize = m_initSize;
 		}
 		}
 
 
-		m_impl = nullptr;
+		ANKI_ASSERT(crntMaxSize > 0);
+
+		// Fix the size
+		crntMaxSize = min(crntMaxSize, (PtrSize)m_maxSize);
 	}
 	}
-}
 
 
-//==============================================================================
-void* ChainMemoryPool::allocate(PtrSize size, PtrSize alignment)
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->allocate(size, alignment);
-}
+	size = max(crntMaxSize, size) + 16;
 
 
-//==============================================================================
-Bool ChainMemoryPool::free(void* ptr)
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->free(ptr);
-}
+	//
+	// Create the chunk
+	//
+	Chunk* chunk = (Chunk*)m_allocCb(
+		m_allocCbUserData, nullptr, sizeof(Chunk), alignof(Chunk));
 
 
-//==============================================================================
-PtrSize ChainMemoryPool::getChunksCount() const
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->getChunksCount();
-}
+	if(chunk)
+	{
+		// Construct it
+		construct(chunk);
+		Error error = chunk->m_pool.create(
+			m_allocCb, m_allocCbUserData, size, m_alignmentBytes);
 
 
-//==============================================================================
-PtrSize ChainMemoryPool::getAllocatedSize() const
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->getAllocatedSize();
-}
+		if(!error)
+		{
+			chunk->m_pool.m_ignoreAllocationErrors = true;
 
 
-//==============================================================================
-U32 ChainMemoryPool::getUsersCount() const
-{
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->m_refcount.load();
+			// Register it
+			if(m_tailChunk)
+			{
+				m_tailChunk->m_next = chunk;
+				m_tailChunk = chunk;
+			}
+			else
+			{
+				ANKI_ASSERT(m_headChunk == nullptr);
+
+				m_headChunk = m_tailChunk = chunk;
+			}
+		}
+		else
+		{
+			destruct(chunk);
+			m_allocCb(m_allocCbUserData, chunk, 0, 0);
+			chunk = nullptr;
+		}
+	}
+	else
+	{
+		ANKI_LOGE("Out of memory");
+	}
+	
+	return chunk;
 }
 }
 
 
 //==============================================================================
 //==============================================================================
-U32 ChainMemoryPool::getAllocationsCount() const
+void* ChainMemoryPool::allocateFromChunk(
+	Chunk* ch, PtrSize size, PtrSize alignment)
 {
 {
-	ANKI_ASSERT(m_impl != nullptr);
-	return m_impl->m_allocationsCount.load();
+	ANKI_ASSERT(ch);
+	ANKI_ASSERT(size <= m_maxSize);
+	void* mem = ch->m_pool.allocate(size, alignment);
+
+	if(mem)
+	{
+		++ch->m_allocationsCount;
+	}
+	else
+	{
+		// Chunk is full. Need a new one
+	}
+
+	return mem;
 }
 }
 
 
 } // end namespace anki
 } // end namespace anki

+ 64 - 0
tests/framework/Framework.h

@@ -113,6 +113,54 @@ extern void deleteTesterSingleton();
 		} \
 		} \
 	} while(0);
 	} while(0);
 
 
+/// Intermediate macro
+#define ANKI_TEST_EXPECT_GT_IMPL(file_, line_, func_, x, y) \
+	do { \
+		if((x) <= (y)) { \
+			std::stringstream ss; \
+			ss << "FAILURE: " << #x << " > " << #y << " (" \
+				<< file_ << ":" << line_ << ")"; \
+			fprintf(stderr, "%s\n", ss.str().c_str()); \
+			abort(); \
+		} \
+	} while(0);
+
+/// Intermediate macro
+#define ANKI_TEST_EXPECT_GEQ_IMPL(file_, line_, func_, x, y) \
+	do { \
+		if((x) < (y)) { \
+			std::stringstream ss; \
+			ss << "FAILURE: " << #x << " >= " << #y << " (" \
+				<< file_ << ":" << line_ << ")"; \
+			fprintf(stderr, "%s\n", ss.str().c_str()); \
+			abort(); \
+		} \
+	} while(0);
+
+/// Intermediate macro
+#define ANKI_TEST_EXPECT_LT_IMPL(file_, line_, func_, x, y) \
+	do { \
+		if((x) >= (y)) { \
+			std::stringstream ss; \
+			ss << "FAILURE: " << #x << " < " << #y << " (" \
+				<< file_ << ":" << line_ << ")"; \
+			fprintf(stderr, "%s\n", ss.str().c_str()); \
+			abort(); \
+		} \
+	} while(0);
+
+/// Intermediate macro
+#define ANKI_TEST_EXPECT_LEQ_IMPL(file_, line_, func_, x, y) \
+	do { \
+		if((x) > (y)) { \
+			std::stringstream ss; \
+			ss << "FAILURE: " << #x << " <= " << #y << " (" \
+				<< file_ << ":" << line_ << ")"; \
+			fprintf(stderr, "%s\n", ss.str().c_str()); \
+			abort(); \
+		} \
+	} while(0);
+
 /// Intermediate macro
 /// Intermediate macro
 #define ANKI_TEST_EXPECT_NEAR_IMPL(file_, line_, func_, x, y, epsilon_) \
 #define ANKI_TEST_EXPECT_NEAR_IMPL(file_, line_, func_, x, y, epsilon_) \
 	do { \
 	do { \
@@ -133,6 +181,22 @@ extern void deleteTesterSingleton();
 #define ANKI_TEST_EXPECT_NEQ(x_, y_) \
 #define ANKI_TEST_EXPECT_NEQ(x_, y_) \
 	ANKI_TEST_EXPECT_NEQ_IMPL(__FILE__, __LINE__, __func__, x_, y_)
 	ANKI_TEST_EXPECT_NEQ_IMPL(__FILE__, __LINE__, __func__, x_, y_)
 
 
+/// Macro to compare greater than
+#define ANKI_TEST_EXPECT_GT(x_, y_) \
+	ANKI_TEST_EXPECT_GT_IMPL(__FILE__, __LINE__, __func__, x_, y_)
+
+/// Macro to compare greater than or equal
+#define ANKI_TEST_EXPECT_GEQ(x_, y_) \
+	ANKI_TEST_EXPECT_GEQ_IMPL(__FILE__, __LINE__, __func__, x_, y_)
+
+/// Macro to compare less than
+#define ANKI_TEST_EXPECT_LT(x_, y_) \
+	ANKI_TEST_EXPECT_LT_IMPL(__FILE__, __LINE__, __func__, x_, y_)
+
+/// Macro to compare less than or equal
+#define ANKI_TEST_EXPECT_LEQ(x_, y_) \
+	ANKI_TEST_EXPECT_LEQ_IMPL(__FILE__, __LINE__, __func__, x_, y_)
+
 /// Compare floats with epsilon
 /// Compare floats with epsilon
 #define ANKI_TEST_EXPECT_NEAR(x_, y_, e_) \
 #define ANKI_TEST_EXPECT_NEAR(x_, y_, e_) \
 	ANKI_TEST_EXPECT_NEAR_IMPL(__FILE__, __LINE__, __func__, x_, y_, e_)
 	ANKI_TEST_EXPECT_NEAR_IMPL(__FILE__, __LINE__, __func__, x_, y_, e_)

+ 20 - 3
tests/util/Allocator.cpp

@@ -28,7 +28,7 @@ ANKI_TEST(Allocators, StackAllocator)
 	{
 	{
 		typedef StackAllocator<Foo, false> All;
 		typedef StackAllocator<Foo, false> All;
 		All alloc(allocAligned, nullptr, 
 		All alloc(allocAligned, nullptr, 
-			(sizeof(Foo) + 1) * 10, 1);
+			(sizeof(Foo) + 1) * 10, alignof(Foo));
 		std::vector<Foo, All> vec(alloc);
 		std::vector<Foo, All> vec(alloc);
 
 
 		vec.reserve(10);
 		vec.reserve(10);
@@ -51,8 +51,25 @@ ANKI_TEST(Allocators, StackAllocator)
 		ANKI_TEST_EXPECT_EQ(sum, sumi);
 		ANKI_TEST_EXPECT_EQ(sum, sumi);
 	}
 	}
 
 
-	ANKI_TEST_EXPECT_EQ(Foo::constructorCallCount, 
-		Foo::destructorCallCount);
+	// Copy around
+	{
+		typedef StackAllocator<Foo, false> Alloc;
+
+		Alloc a(allocAligned, nullptr, 
+			(sizeof(Foo) + 1) * 10, alignof(Foo));
+
+		Alloc b(allocAligned, nullptr, 
+			(sizeof(Foo) + 1) * 10, alignof(Foo));
+
+		a = b;
+		ANKI_TEST_EXPECT_EQ(a.getMemoryPool().getUsersCount(), 2);
+
+		b = a;
+		ANKI_TEST_EXPECT_EQ(&a.getMemoryPool(), &b.getMemoryPool());
+		ANKI_TEST_EXPECT_EQ(a.getMemoryPool().getUsersCount(), 2);
+	}
+
+	ANKI_TEST_EXPECT_EQ(Foo::constructorCallCount, Foo::destructorCallCount);
 
 
 	// End
 	// End
 	Foo::reset();
 	Foo::reset();

+ 49 - 22
tests/util/Memory.cpp

@@ -10,31 +10,58 @@
 
 
 ANKI_TEST(Memory, StackMemoryPool)
 ANKI_TEST(Memory, StackMemoryPool)
 {
 {
-	/*constexpr U n = 4;
-	StackAllocator<U8, true> alloc(sizeof(Foo) * n + sizeof(PtrSize));
-
-	Foo* f = ANKI_NEW(Foo, alloc, 123);
-
-	ANKI_TEST_EXPECT_EQ(f->x, 123);
-
-	ANKI_TEST_EXPECT_EQ(alloc.getMemoryPool().getAllocatedSize(),
-		sizeof(Foo) + sizeof(U32));
-
-	ANKI_DELETE(f, alloc);
-
-	ANKI_TEST_EXPECT_EQ(alloc.getMemoryPool().getAllocatedSize(), 0);
-
-	// Array
-	f = ANKI_NEW_ARRAY(Foo, alloc, 2, 123);
-
-	ANKI_TEST_EXPECT_EQ(alloc.getMemoryPool().getAllocatedSize(),
-		2 * sizeof(Foo) + sizeof(U32) + sizeof(PtrSize));
+	// Create/destroy test
+	{
+		StackMemoryPool pool;
+	}
 
 
-	ANKI_TEST_EXPECT_NEQ(alloc.getMemoryPool().getAllocatedSize(), 0);
+	// Create/destroy test #2
+	{
+		StackMemoryPool pool;
 
 
-	ANKI_DELETE_ARRAY(f, alloc);
+		ANKI_TEST_EXPECT_NO_ERR(pool.create(allocAligned, nullptr, 10, 4));
+	}
 
 
-	ANKI_TEST_EXPECT_EQ(alloc.getMemoryPool().getAllocatedSize(), 0);*/
+	// Allocate
+	{
+		StackMemoryPool pool;
+
+		ANKI_TEST_EXPECT_NO_ERR(pool.create(allocAligned, nullptr, 100, 4));
+
+		void* a = pool.allocate(25, 1);
+		ANKI_TEST_EXPECT_NEQ(a, nullptr);
+		ANKI_TEST_EXPECT_EQ(pool.getAllocationsCount(), 1);
+		ANKI_TEST_EXPECT_GEQ(pool.getAllocatedSize(), 25);
+
+		ANKI_TEST_EXPECT_EQ(pool.free(a), true);
+		ANKI_TEST_EXPECT_EQ(pool.getAllocationsCount(), 0);
+
+		// Allocate a few
+		a = pool.allocate(25, 1);
+		ANKI_TEST_EXPECT_NEQ(a, nullptr);
+		a = pool.allocate(25, 1);
+		ANKI_TEST_EXPECT_NEQ(a, nullptr);
+		a = pool.allocate(25, 1);
+		ANKI_TEST_EXPECT_NEQ(a, nullptr);
+		a = pool.allocate(25, 1);
+		ANKI_TEST_EXPECT_EQ(a, nullptr);
+		ANKI_TEST_EXPECT_EQ(pool.getAllocationsCount(), 3);
+
+		// Reset
+		pool.reset();
+		ANKI_TEST_EXPECT_EQ(pool.getAllocationsCount(), 0);
+
+		// Allocate again
+		a = pool.allocate(25, 1);
+		ANKI_TEST_EXPECT_NEQ(a, nullptr);
+		a = pool.allocate(25, 1);
+		ANKI_TEST_EXPECT_NEQ(a, nullptr);
+		a = pool.allocate(25, 1);
+		ANKI_TEST_EXPECT_NEQ(a, nullptr);
+		a = pool.allocate(25, 1);
+		ANKI_TEST_EXPECT_EQ(a, nullptr);
+		ANKI_TEST_EXPECT_EQ(pool.getAllocationsCount(), 3);
+	}
 }
 }
 
 
 ANKI_TEST(Memory, ChainMemoryPool)
 ANKI_TEST(Memory, ChainMemoryPool)