Browse Source

Making the stack memory pool more flexible

Panagiotis Christopoulos Charitos 10 years ago
parent
commit
15d0e69b49

+ 0 - 1
include/anki/resource/ResourceManager.h

@@ -115,7 +115,6 @@ public:
 		CString m_cacheDir;
 		AllocAlignedCallback m_allocCallback = 0;
 		void* m_allocCallbackData = nullptr;
-		U32 m_tempAllocatorMemorySize = 1 * 1024 * 1024;
 	};
 
 	ResourceManager();

+ 0 - 1
include/anki/scene/SceneGraph.h

@@ -42,7 +42,6 @@ public:
 	ANKI_USE_RESULT Error init(
 		AllocAlignedCallback allocCb,
 		void* allocCbData,
-		U32 frameAllocatorSize,
 		ThreadPool* threadpool,
 		ResourceManager* resources,
 		Input* input,

+ 71 - 1
include/anki/util/Atomic.h

@@ -27,7 +27,7 @@ enum class AtomicMemoryOrder
 #endif
 };
 
-/// Atomic template.
+/// Atomic template. At the moment it doesn't work well with pointers.
 template<typename T, AtomicMemoryOrder tmemOrd = AtomicMemoryOrder::RELAXED>
 class Atomic: public NonCopyable
 {
@@ -35,6 +35,7 @@ public:
 	using Value = T;
 	static constexpr AtomicMemoryOrder MEMORY_ORDER = tmemOrd;
 
+	/// It will set it to zero.
 	Atomic()
 		: m_val(static_cast<Value>(0))
 	{}
@@ -43,6 +44,7 @@ public:
 		: m_val(a)
 	{}
 
+	/// Get the value of the atomic.
 	Value load(AtomicMemoryOrder memOrd = MEMORY_ORDER) const
 	{
 #if defined(__GNUC__)
@@ -52,6 +54,17 @@ public:
 #endif
 	}
 
+	/// @copybrief load
+	Value load(AtomicMemoryOrder memOrd = MEMORY_ORDER) const volatile
+	{
+#if defined(__GNUC__)
+		return __atomic_load_n(&m_val, static_cast<int>(memOrd));
+#else
+#	error "TODO"
+#endif
+	}
+
+	/// Store
 	void store(const Value a, AtomicMemoryOrder memOrd = MEMORY_ORDER)
 	{
 #if defined(__GNUC__)
@@ -61,6 +74,17 @@ public:
 #endif
 	}
 
+	/// @copybrief store
+	void store(const Value a, AtomicMemoryOrder memOrd = MEMORY_ORDER) volatile
+	{
+#if defined(__GNUC__)
+		__atomic_store_n(&m_val, a, static_cast<int>(memOrd));
+#else
+#	error "TODO"
+#endif
+	}
+
+	/// Fetch and add.
 	template<typename Y>
 	Value fetchAdd(const Y a, AtomicMemoryOrder memOrd = MEMORY_ORDER)
 	{
@@ -71,6 +95,18 @@ public:
 #endif
 	}
 
+	/// @copybrief fetchAdd
+	template<typename Y>
+	Value fetchAdd(const Y a, AtomicMemoryOrder memOrd = MEMORY_ORDER) volatile
+	{
+#if defined(__GNUC__)
+		return __atomic_fetch_add(&m_val, a, static_cast<int>(memOrd));
+#else
+#	error "TODO"
+#endif
+	}
+
+	/// Fetch and subtract.
 	template<typename Y>
 	Value fetchSub(const Y a, AtomicMemoryOrder memOrd = MEMORY_ORDER)
 	{
@@ -81,6 +117,17 @@ public:
 #endif
 	}
 
+	/// @copybrief fetchSub
+	template<typename Y>
+	Value fetchSub(const Y a, AtomicMemoryOrder memOrd = MEMORY_ORDER) volatile
+	{
+#if defined(__GNUC__)
+		return __atomic_fetch_sub(&m_val, a, static_cast<int>(memOrd));
+#else
+#	error "TODO"
+#endif
+	}
+
 	/// @code
 	/// if(m_val == expected) {
 	/// 	m_val = desired;
@@ -101,6 +148,18 @@ public:
 #endif
 	}
 
+	/// @copybrief compareExchange
+	Bool compareExchange(Value& expected, const Value desired,
+		AtomicMemoryOrder memOrd = MEMORY_ORDER) volatile
+	{
+#if defined(__GNUC__)
+		return __atomic_compare_exchange_n(&m_val, &expected, desired,
+			false, static_cast<int>(memOrd), __ATOMIC_RELAXED);
+#else
+#	error "TODO"
+#endif
+	}
+
 	/// Set @a a to the atomic and return the previous value.
 	Value exchange(const Value a, AtomicMemoryOrder memOrd = MEMORY_ORDER)
 	{
@@ -108,6 +167,17 @@ public:
 		return __atomic_exchange_n(&m_val, a, static_cast<int>(memOrd));
 #else
 #	error "TODO"
+#endif
+	}
+
+	/// @copybrief exchange
+	Value exchange(const Value a,
+		AtomicMemoryOrder memOrd = MEMORY_ORDER) volatile
+	{
+#if defined(__GNUC__)
+		return __atomic_exchange_n(&m_val, a, static_cast<int>(memOrd));
+#else
+#	error "TODO"
 #endif
 	}
 

+ 57 - 44
include/anki/util/Memory.h

@@ -9,6 +9,8 @@
 #include <anki/util/NonCopyable.h>
 #include <anki/util/Atomic.h>
 #include <anki/util/Assert.h>
+#include <anki/util/Array.h>
+#include <anki/util/Thread.h>
 #include <utility> // For forward
 
 namespace anki {
@@ -74,8 +76,7 @@ public:
 	/// @return The allocated memory or nullptr on failure
 	void* allocate(PtrSize size, PtrSize alignmentBytes);
 
-	/// Free memory. If the ptr is not the last allocation of the chunk
-	/// then nothing happens and the method returns false
+	/// Free memory.
 	/// @param[in, out] ptr Memory block to deallocate
 	void free(void* ptr);
 
@@ -179,7 +180,9 @@ public:
 	/// Create with parameters
 	/// @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 initialChunkSize The size of the first chunk.
+	/// @param nextChunkScale Value that controls the next chunk.
+	/// @param nextChunkBias Value that controls the next chunk.
 	/// @param ignoreDeallocationErrors Method free() may fail if the ptr is
 	///        not in the top of the stack. Set that to true to suppress such
 	///        errors
@@ -188,7 +191,9 @@ public:
 	void create(
 		AllocAlignedCallback allocCb,
 		void* allocCbUserData,
-		PtrSize size,
+		PtrSize initialChunkSize,
+		F32 nextChunkScale = 2.0,
+		PtrSize nextChunkBias = 0,
 		Bool ignoreDeallocationErrors = true,
 		PtrSize alignmentBytes = ANKI_SAFE_ALIGNMENT);
 
@@ -198,63 +203,71 @@ public:
 	/// @return The allocated memory or nullptr on failure
 	void* allocate(PtrSize size, PtrSize alignmentBytes);
 
-	/// Free memory in StackMemoryPool. If the ptr is not the last allocation
-	/// then nothing happens and the method returns false. The operation is
-	/// threadsafe
+	/// Free memory in StackMemoryPool. It will not actially free anything.
 	/// @param[in, out] ptr Memory block to deallocate
 	void free(void* ptr);
 
-	/// 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
-	/// pool's state later on. Not recommended on multithreading scenarios
-	/// @return The current state of the pool
-	Snapshot getShapshot() const;
-
-	/// Reset the poll using a snapshot. Not recommended on multithreading
-	/// scenarios
-	/// @param s The snapshot to be used
-	void resetUsingSnapshot(Snapshot s);
-
 	/// Reinit the pool. All existing allocated memory will be lost
 	void reset();
 
 private:
-	/// The header of each allocation
-	struct MemoryBlockHeader
+	/// The memory chunk.
+	class Chunk
 	{
-		U8 m_size[sizeof(U32)]; ///< It's U8 to allow whatever alignment
+	public:
+		/// The base memory of the chunk.
+		U8* m_baseMem = nullptr;
+
+		/// The moving ptr for the next allocation.
+		Atomic<U8*> m_mem = {nullptr};
+
+		/// The chunk size.
+		PtrSize m_size = 0;
+
+		/// Check that it's initialized.
+		void check() const
+		{
+			ANKI_ASSERT(m_baseMem != nullptr);
+			ANKI_ASSERT(m_mem.load() >= m_baseMem);
+			ANKI_ASSERT(m_size > 0);
+		}
+
+		// Check that it's in reset state.
+		void checkReset() const
+		{
+			ANKI_ASSERT(m_baseMem != nullptr);
+			ANKI_ASSERT(m_mem.load() == m_baseMem);
+			ANKI_ASSERT(m_size > 0);
+		}
 	};
 
-	static_assert(alignof(MemoryBlockHeader) == 1, "Alignment error");
-	static_assert(sizeof(MemoryBlockHeader) == sizeof(U32), "Size error");
-
 	/// Alignment of allocations
 	PtrSize m_alignmentBytes = 0;
 
-	/// Aligned size of MemoryBlockHeader
-	PtrSize m_headerSize = 0;
-
-	/// Pre-allocated memory chunk.
-	U8* m_memory = nullptr;
+	/// The size of the first chunk.
+	PtrSize m_initialChunkSize = 0;
 
-	/// Size of the pre-allocated memory chunk
-	PtrSize m_memsize = 0;
+	/// Chunk scale.
+	F32 m_nextChunkScale = 0.0;
 
-	/// Points to the memory and more specifically to the top of the stack
-	Atomic<U8*> m_top = {nullptr};
+	/// Chunk bias.
+	PtrSize m_nextChunkBias = 0;
 
+	/// Ignore deallocation errors.
 	Bool8 m_ignoreDeallocationErrors = false;
+
+	/// The current chunk. Chose the more strict memory order to avoid compiler
+	/// re-ordering of instructions
+	Atomic<U32, AtomicMemoryOrder::SEQ_CST> m_crntChunkIdx = {0};
+
+	/// The max number of chunks.
+	static const U MAX_CHUNKS = 256;
+
+	/// The chunks.
+	Array<Chunk, MAX_CHUNKS> m_chunks;
+
+	/// Protect the m_crntChunkIdx.
+	Mutex m_lock;
 };
 
 /// Chain memory pool. Almost similar to StackMemoryPool but more flexible and

+ 6 - 4
src/core/App.cpp

@@ -260,7 +260,6 @@ Error App::createInternal(const ConfigSet& config_,
 	rinit.m_cacheDir = m_cacheDir.toCString();
 	rinit.m_allocCallback = m_allocCb;
 	rinit.m_allocCallbackData = m_allocCbData;
-	rinit.m_tempAllocatorMemorySize = 1024 * 1024 * 5;
 	m_resources = m_heapAlloc.newInstance<ResourceManager>();
 
 	ANKI_CHECK(m_resources->create(rinit));
@@ -289,14 +288,17 @@ Error App::createInternal(const ConfigSet& config_,
 		m_renderer->getMaterialShaderSource().toCString());
 	m_resources->setRenderer(&m_renderer->getOffscreenRenderer());
 
+	//
 	// Scene
+	//
 	m_scene = m_heapAlloc.newInstance<SceneGraph>();
 
-	ANKI_CHECK(m_scene->init(m_allocCb, m_allocCbData,
-		config.getNumber("sceneFrameAllocatorSize"), m_threadpool, m_resources,
-		m_input, &m_globalTimestamp, config));
+	ANKI_CHECK(m_scene->init(m_allocCb, m_allocCbData, m_threadpool,
+		m_resources, m_input, &m_globalTimestamp, config));
 
+	//
 	// Script
+	//
 	m_script = m_heapAlloc.newInstance<ScriptManager>();
 
 	ANKI_CHECK(m_script->create(m_allocCb, m_allocCbData, m_scene));

+ 1 - 1
src/renderer/MainRenderer.cpp

@@ -46,7 +46,7 @@ Error MainRenderer::create(
 
 	m_alloc = HeapAllocator<U8>(allocCb, allocCbUserData);
 	m_frameAlloc = StackAllocator<U8>(allocCb, allocCbUserData,
-		1024 * 1024 * 10);
+		1024 * 1024 * 1);
 
 	// Init default FB
 	m_width = config.getNumber("width");

+ 1 - 1
src/resource/ResourceManager.cpp

@@ -45,7 +45,7 @@ Error ResourceManager::create(Initializer& init)
 
 	m_tmpAlloc = TempResourceAllocator<U8>(
 		init.m_allocCallback, init.m_allocCallbackData,
-		init.m_tempAllocatorMemorySize);
+		10 * 1024 * 1024);
 
 	m_cacheDir.create(m_alloc, init.m_cacheDir);
 

+ 1 - 2
src/scene/SceneGraph.cpp

@@ -150,7 +150,6 @@ SceneGraph::~SceneGraph()
 Error SceneGraph::init(
 	AllocAlignedCallback allocCb,
 	void* allocCbData,
-	U32 frameAllocatorSize,
 	ThreadPool* threadpool,
 	ResourceManager* resources,
 	Input* input,
@@ -174,7 +173,7 @@ Error SceneGraph::init(
 		1.0,
 		0);
 	m_frameAlloc = SceneFrameAllocator<U8>(
-		allocCb, allocCbData, frameAllocatorSize);
+		allocCb, allocCbData, 1 * 1024 * 1024);
 
 	err = m_events.create(this);
 

+ 157 - 118
src/util/Memory.cpp

@@ -40,6 +40,14 @@ static Signature computeSignature(void* ptr)
 #define ANKI_CREATION_OOM_ACTION() ANKI_LOGF("Out of memory")
 #define ANKI_OOM_ACTION() ANKI_LOGE("Out of memory. Expect segfault")
 
+template<typename TPtr, typename TSize>
+static void invalidateMemory(TPtr ptr, TSize size)
+{
+#if ANKI_DEBUG
+	memset(static_cast<void*>(ptr), 0xCC, size);
+#endif
+}
+
 //==============================================================================
 // Other                                                                       =
 //==============================================================================
@@ -297,48 +305,63 @@ StackMemoryPool::StackMemoryPool()
 //==============================================================================
 StackMemoryPool::~StackMemoryPool()
 {
-	if(m_memory != nullptr)
+	// Iterate all until you find an unused
+	for(Chunk& ch : m_chunks)
 	{
-#if ANKI_DEBUG
-		// Invalidate the memory
-		memset(m_memory, 0xCC, m_memsize);
-#endif
-		m_allocCb(m_allocCbUserData, m_memory, 0, 0);
+		if(ch.m_baseMem != nullptr)
+		{
+			ch.check();
+
+			invalidateMemory(ch.m_baseMem, ch.m_size);
+			m_allocCb(m_allocCbUserData, ch.m_baseMem, 0, 0);
+		}
+		else
+		{
+			break;
+		}
+	}
+
+	// Do some error checks
+	auto allocCount = m_allocationsCount.load();
+	if(!m_ignoreDeallocationErrors && allocCount != 0)
+	{
+		ANKI_LOGW("Forgot to deallocate");
 	}
 }
 
 //==============================================================================
 void StackMemoryPool::create(
 	AllocAlignedCallback allocCb, void* allocCbUserData,
-	PtrSize size, Bool ignoreDeallocationErrors, PtrSize alignmentBytes)
+	PtrSize initialChunkSize, F32 nextChunkScale, PtrSize nextChunkBias,
+	Bool ignoreDeallocationErrors, PtrSize alignmentBytes)
 {
 	ANKI_ASSERT(!isCreated());
 	ANKI_ASSERT(allocCb);
-	ANKI_ASSERT(size > 0);
+	ANKI_ASSERT(initialChunkSize > 0);
+	ANKI_ASSERT(nextChunkScale >= 1.0);
 	ANKI_ASSERT(alignmentBytes > 0);
 
 	m_allocCb = allocCb;
 	m_allocCbUserData = allocCbUserData;
 	m_alignmentBytes = alignmentBytes;
-	m_memsize = getAlignedRoundUp(alignmentBytes, size);
+	m_initialChunkSize = initialChunkSize;
+	m_nextChunkScale = nextChunkScale;
+	m_nextChunkBias = nextChunkBias;
 	m_ignoreDeallocationErrors = ignoreDeallocationErrors;
 
-	m_memory = static_cast<U8*>(m_allocCb(
-		m_allocCbUserData, nullptr, m_memsize, m_alignmentBytes));
+	// Create the first chunk
+	void* mem = m_allocCb(
+		m_allocCbUserData, nullptr, m_initialChunkSize, m_alignmentBytes);
 
-	if(m_memory != nullptr)
+	if(mem != nullptr)
 	{
-#if ANKI_DEBUG
-		// Invalidate the memory
-		memset(m_memory, 0xCC, m_memsize);
-#endif
+		invalidateMemory(mem, m_initialChunkSize);
 
-		// Align allocated memory
-		m_top.store(m_memory);
+		m_chunks[0].m_baseMem = static_cast<U8*>(mem);
+		m_chunks[0].m_mem.store(m_chunks[0].m_baseMem);
+		m_chunks[0].m_size = initialChunkSize;
 
-		// Calc header size
-		m_headerSize =
-			getAlignedRoundUp(m_alignmentBytes, sizeof(MemoryBlockHeader));
+		ANKI_ASSERT(m_crntChunkIdx.load() == 0);
 	}
 	else
 	{
@@ -353,41 +376,94 @@ void* StackMemoryPool::allocate(PtrSize size, PtrSize alignment)
 	ANKI_ASSERT(alignment <= m_alignmentBytes);
 	(void)alignment;
 
-	size = getAlignedRoundUp(m_alignmentBytes, size + m_headerSize);
-
-	ANKI_ASSERT(size < MAX_U32 && "Too big allocation");
+	size = getAlignedRoundUp(m_alignmentBytes, size);
+	ANKI_ASSERT(size > 0);
+	ANKI_ASSERT(size <= m_initialChunkSize && "The chunks should have enough "
+		"space to hold at least one allocation");
 
-	U8* out = m_top.fetchAdd(size);
+	Chunk* crntChunk = nullptr;
+	Bool retry = true;
+	U8* out = nullptr;
 
-	if(out + size <= m_memory + m_memsize)
+	do
 	{
-#if ANKI_DEBUG
-		// Invalidate the block
-		memset(out, 0xCC, size);
-#endif
-
-		// Write the block header
-		MemoryBlockHeader* header =
-			reinterpret_cast<MemoryBlockHeader*>(out);
-		U32 size32 = size;
-		memcpy(&header->m_size[0], &size32, sizeof(U32));
+		crntChunk = &m_chunks[m_crntChunkIdx.load()];
+		crntChunk->check();
 
-		// Set the correct output
-		out += m_headerSize;
+		out = crntChunk->m_mem.fetchAdd(size);
+		ANKI_ASSERT(out >= crntChunk->m_baseMem);
 
-		// Check alignment
-		ANKI_ASSERT(isAligned(m_alignmentBytes, out));
+		if(PtrSize(out + size - crntChunk->m_baseMem) <= crntChunk->m_size)
+		{
+			// All is fine, there is enough space in the chunk
 
-		// Increase count
-		m_allocationsCount.fetchAdd(1);
-	}
-	else
-	{
-		ANKI_OOM_ACTION();
-		out = nullptr;
-	}
+			retry = false;
+			m_allocationsCount.fetchAdd(1);
+		}
+		else
+		{
+			// Need new chunk
+
+			LockGuard<Mutex> lock(m_lock);
+
+			// Make sure that only one thread will create a new chunk
+			if(&m_chunks[m_crntChunkIdx.load()] == crntChunk)
+			{
+				// We can create a new chunk
+
+				PtrSize oldChunkSize = crntChunk->m_size;
+				++crntChunk;
+				if(crntChunk >= m_chunks.getEnd())
+				{
+					ANKI_LOGE("Number of chunks is not enough. Expect a crash");
+				}
+
+				if(crntChunk->m_baseMem == nullptr)
+				{
+					// Need to create a new chunk
+
+					PtrSize newChunkSize =
+						oldChunkSize * m_nextChunkScale + m_nextChunkBias;
+					alignRoundUp(m_alignmentBytes, newChunkSize);
+
+					void* mem = m_allocCb(m_allocCbUserData, nullptr,
+						newChunkSize, m_alignmentBytes);
+
+					if(mem != nullptr)
+					{
+						invalidateMemory(mem, newChunkSize);
+
+						crntChunk->m_baseMem = static_cast<U8*>(mem);
+						crntChunk->m_mem.store(crntChunk->m_baseMem);
+						crntChunk->m_size = newChunkSize;
+
+						U idx = m_crntChunkIdx.fetchAdd(1);
+						ANKI_ASSERT(&m_chunks[idx] == crntChunk - 1);
+						(void)idx;
+					}
+					else
+					{
+						out = nullptr;
+						retry = false;
+						ANKI_OOM_ACTION();
+					}
+				}
+				else
+				{
+					// Need to recycle one
+
+					crntChunk->checkReset();
+					invalidateMemory(crntChunk->m_baseMem, crntChunk->m_size);
+
+					U idx = m_crntChunkIdx.fetchAdd(1);
+					ANKI_ASSERT(&m_chunks[idx] == crntChunk - 1);
+					(void)idx;
+				}
+			}
+		}
+	} while(retry);
 
-	return out;
+	return static_cast<void*>(out);
 }
 
 //==============================================================================
@@ -399,44 +475,9 @@ void StackMemoryPool::free(void* ptr)
 	// 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 = reinterpret_cast<U8*>(ptr) - m_headerSize;
-
-	// realptr should be inside the pool's preallocated memory
-	ANKI_ASSERT(realptr >= m_memory);
-
-	// Get block size
-	MemoryBlockHeader* header = reinterpret_cast<MemoryBlockHeader*>(realptr);
-	U32 size;
-	memcpy(&size, &header->m_size[0], sizeof(U32));
-
-	// Check if the size is within limits
-	ANKI_ASSERT(realptr + size <= m_memory + m_memsize);
-
-	// Atomic stuff
-	U8* expected = realptr + size;
-	U8* desired = realptr;
-
-	// if(top == expected) {
-	//     top = desired;
-	//     exchange = true;
-	// } else {
-	//     expected = top;
-	//     exchange = false;
-	// }
-	Bool exchange = m_top.compareExchange(expected, desired);
-
-	// Decrease count
-	m_allocationsCount.fetchSub(1);
-
-	// Error if needed
-	if(!m_ignoreDeallocationErrors && !exchange)
-	{
-		ANKI_LOGW("Not top of stack. Deallocation failed. Silently ignoring");
-	}
+	auto count = m_allocationsCount.fetchSub(1);
+	ANKI_ASSERT(count > 0);
+	(void)count;
 }
 
 //==============================================================================
@@ -444,33 +485,32 @@ void StackMemoryPool::reset()
 {
 	ANKI_ASSERT(isCreated());
 
-	// memory is nullptr if moved
-	ANKI_ASSERT(m_memory != nullptr);
-
-#if ANKI_DEBUG
-	// Invalidate the memory
-	memset(m_memory, 0xCC, m_memsize);
-#endif
-
-	m_top.store(m_memory);
-	m_allocationsCount.store(0);
-}
+	// Iterate all until you find an unused
+	for(Chunk& ch : m_chunks)
+	{
+		if(ch.m_baseMem != nullptr)
+		{
+			ch.check();
+			ch.m_mem.store(ch.m_baseMem);
 
-//==============================================================================
-StackMemoryPool::Snapshot StackMemoryPool::getShapshot() const
-{
-	ANKI_ASSERT(isCreated());
-	return m_top.load();
-}
+			invalidateMemory(ch.m_baseMem, ch.m_size);
+		}
+		else
+		{
+			break;
+		}
+	}
 
-//==============================================================================
-void StackMemoryPool::resetUsingSnapshot(Snapshot s)
-{
-	ANKI_ASSERT(isCreated());
-	ANKI_ASSERT(static_cast<U8*>(s) >= m_memory);
-	ANKI_ASSERT(static_cast<U8*>(s) < m_memory + m_memsize);
+	// Set the crnt chunk
+	m_chunks[0].checkReset();
+	m_crntChunkIdx.store(0);
 
-	m_top.store(static_cast<U8*>(s));
+	// Reset allocation count and do some error checks
+	auto allocCount = m_allocationsCount.exchange(0);
+	if(!m_ignoreDeallocationErrors && allocCount != 0)
+	{
+		ANKI_LOGW("Forgot to deallocate");
+	}
 }
 
 //==============================================================================
@@ -516,7 +556,9 @@ void ChainMemoryPool::create(
 	PtrSize alignmentBytes)
 {
 	ANKI_ASSERT(!isCreated());
-	ANKI_ASSERT(m_allocCb == nullptr);
+	ANKI_ASSERT(initialChunkSize > 0);
+	ANKI_ASSERT(nextChunkScale >= 1.0);
+	ANKI_ASSERT(alignmentBytes > 0);
 
 	// Set all values
 	m_allocCb = allocCb;
@@ -691,9 +733,8 @@ ChainMemoryPool::Chunk* ChainMemoryPool::createNewChunk(PtrSize size)
 
 	if(chunk)
 	{
-#if ANKI_DEBUG
-		memset(chunk, 0xCC, allocationSize);
-#endif
+		invalidateMemory(chunk, allocationSize);
+
 		// Construct it
 		memset(chunk, 0, sizeof(Chunk));
 
@@ -779,10 +820,8 @@ void ChainMemoryPool::destroyChunk(Chunk* ch)
 		ch->m_next->m_prev = ch->m_prev;
 	}
 
-#if ANKI_DEBUG
-	memset(ch, 0xCC,
+	invalidateMemory(ch,
 		getAlignedRoundUp(m_alignmentBytes, sizeof(Chunk)) + ch->m_memsize);
-#endif
 	m_allocCb(m_allocCbUserData, ch, 0, 0);
 }
 

+ 5 - 5
testapp/Main.cpp

@@ -42,7 +42,7 @@ App* app;
 ModelNode* horse;
 PerspectiveCamera* cam;
 
-#define PLAYER 0
+#define PLAYER 1
 #define MOUSE 1
 
 Bool profile = false;
@@ -488,7 +488,7 @@ Error initSubsystems(int argc, char* argv[])
 	config.set("is.sm.poissonEnabled", true);
 	config.set("is.sm.resolution", 1024);
 	config.set("lf.maxFlares", 32);
-	config.set("pps.enabled", false);
+	config.set("pps.enabled", true);
 	config.set("pps.bloom.enabled", true);
 	config.set("pps.bloom.renderingQuality", 0.5);
 	config.set("pps.bloom.blurringDist", 1.0);
@@ -505,8 +505,8 @@ Error initSubsystems(int argc, char* argv[])
 	config.set("pps.sslf.enabled", true);
 	config.set("pps.sharpen", true);
 	config.set("renderingQuality", 1.0);
-	config.set("width", 1280);
-	config.set("height", 720);
+	config.set("width", 1920);
+	config.set("height", 1080);
 	config.set("lodDistance", 20.0);
 	config.set("samples", 1);
 	config.set("tessellation", true);
@@ -517,7 +517,7 @@ Error initSubsystems(int argc, char* argv[])
 	config.set("sslr.enabled", false);
 	config.set("ir.rendererSize", 64);
 	config.set("ir.clusterSizeZ", 16);
-	config.set("fullscreenDesktopResolution", true);
+	config.set("fullscreenDesktopResolution", false);
 	//config.set("clusterSizeZ", 16);
 	config.set("debugContext", false);
 	if(getenv("ANKI_DATA_PATH"))

+ 89 - 9
tests/util/Memory.cpp

@@ -6,7 +6,9 @@
 #include "tests/framework/Framework.h"
 #include "tests/util/Foo.h"
 #include "anki/util/Memory.h"
+#include "anki/util/Thread.h"
 #include <type_traits>
+#include <cstring>
 
 ANKI_TEST(Util, HeapMemoryPool)
 {
@@ -44,25 +46,24 @@ ANKI_TEST(Util, StackMemoryPool)
 	{
 		StackMemoryPool pool;
 
-		pool.create(allocAligned, nullptr, 10, 4);
+		pool.create(allocAligned, nullptr, 10);
 	}
 
 	// Allocate
 	{
 		StackMemoryPool pool;
 
-		pool.create(allocAligned, nullptr, 100, false, 4);
+		pool.create(allocAligned, nullptr, 100, 1.0, 0, true);
 
 		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);
 
 		pool.free(a);
 		ANKI_TEST_EXPECT_EQ(pool.getAllocationsCount(), 0);
 
 		// Allocate a few
-		const U SIZE = 25 - 4;
+		const U SIZE = 75;
 		a = pool.allocate(SIZE, 1);
 		ANKI_TEST_EXPECT_NEQ(a, nullptr);
 		a = pool.allocate(SIZE, 1);
@@ -70,8 +71,8 @@ ANKI_TEST(Util, StackMemoryPool)
 		a = pool.allocate(SIZE, 1);
 		ANKI_TEST_EXPECT_NEQ(a, nullptr);
 		a = pool.allocate(SIZE, 1);
-		ANKI_TEST_EXPECT_EQ(a, nullptr);
-		ANKI_TEST_EXPECT_EQ(pool.getAllocationsCount(), 3);
+		ANKI_TEST_EXPECT_NEQ(a, nullptr);
+		ANKI_TEST_EXPECT_EQ(pool.getAllocationsCount(), 4);
 
 		// Reset
 		pool.reset();
@@ -85,8 +86,87 @@ ANKI_TEST(Util, StackMemoryPool)
 		a = pool.allocate(SIZE, 1);
 		ANKI_TEST_EXPECT_NEQ(a, nullptr);
 		a = pool.allocate(SIZE, 1);
-		ANKI_TEST_EXPECT_EQ(a, nullptr);
-		ANKI_TEST_EXPECT_EQ(pool.getAllocationsCount(), 3);
+		ANKI_TEST_EXPECT_NEQ(a, nullptr);
+		ANKI_TEST_EXPECT_EQ(pool.getAllocationsCount(), 4);
+	}
+
+	// Parallel
+	{
+		StackMemoryPool pool;
+		const U THREAD_COUNT = 32;
+		const U ALLOC_SIZE = 25;
+		ThreadPool threadPool(THREAD_COUNT);
+
+		class AllocateTask: public ThreadPool::Task
+		{
+		public:
+			StackMemoryPool* m_pool = nullptr;
+			Array<void*, 0xF> m_allocations;
+
+			Error operator()(U32 taskId, PtrSize threadsCount)
+			{
+				for(U i = 0; i < m_allocations.getSize(); ++i)
+				{
+					void* ptr = m_pool->allocate(ALLOC_SIZE, 1);
+					memset(ptr, (taskId << 4) | i, ALLOC_SIZE);
+					m_allocations[i] = ptr;
+				}
+
+				return ErrorCode::NONE;
+			}
+		};
+
+		pool.create(allocAligned, nullptr, 100, 1.0, 0, true);
+		Array<AllocateTask, THREAD_COUNT> tasks;
+
+		for(U i = 0; i < THREAD_COUNT; ++i)
+		{
+			tasks[i].m_pool = &pool;
+			threadPool.assignNewTask(i, &tasks[i]);
+		}
+
+		ANKI_TEST_EXPECT_NO_ERR(threadPool.waitForAllThreadsToFinish());
+
+		// Check
+		for(U i = 0; i < THREAD_COUNT; ++i)
+		{
+			const auto& task = tasks[i];
+
+			for(U j = 0; j < task.m_allocations.getSize(); ++j)
+			{
+				U8 magic = (i << 4) | j;
+				U8* ptr = static_cast<U8*>(task.m_allocations[j]);
+
+				for(U k = 0; k < ALLOC_SIZE; ++k)
+				{
+					ANKI_TEST_EXPECT_EQ(ptr[k], magic);
+				}
+			}
+		}
+
+		// Reset and allocate again
+		pool.reset();
+		for(U i = 0; i < THREAD_COUNT; ++i)
+		{
+			threadPool.assignNewTask(i, &tasks[i]);
+		}
+		ANKI_TEST_EXPECT_NO_ERR(threadPool.waitForAllThreadsToFinish());
+
+		for(U i = 0; i < THREAD_COUNT; ++i)
+		{
+			const auto& task = tasks[i];
+
+			for(U j = 0; j < task.m_allocations.getSize(); ++j)
+			{
+				U8 magic = (i << 4) | j;
+				U8* ptr = static_cast<U8*>(task.m_allocations[j]);
+
+				for(U k = 0; k < ALLOC_SIZE; ++k)
+				{
+					ANKI_TEST_EXPECT_EQ(ptr[k], magic);
+				}
+			}
+		}
 	}
 }
 
@@ -116,7 +196,7 @@ ANKI_TEST(Util, ChainMemoryPool)
 	{
 		const U size = sizeof(PtrSize) + 10;
 		ChainMemoryPool pool;
-		
+
 		pool.create(
 			allocAligned, nullptr,
 			size, 2.0, 0, 1);