Browse Source

Some work on the GPU stack allocator

Panagiotis Christopoulos Charitos 9 years ago
parent
commit
21f30db78b

+ 2 - 0
.travis.yml

@@ -5,6 +5,8 @@ sudo: required
 compiler:
   - gcc
 
+dist: trusty
+
 before_install:
   - sudo add-apt-repository -y ppa:ubuntu-toolchain-r/test
   - sudo apt-get update -qq

+ 1 - 1
samples/simple_scene/Main.cpp

@@ -17,7 +17,7 @@ Error MyApp::init()
 	// Init the super class
 	Config config;
 	config.set("fullscreenDesktopResolution", true);
-	config.set("dataPaths", ".:..:../assets");
+	config.set("dataPaths", ".:..");
 	config.set("debugContext", 0);
 	ANKI_CHECK(App::init(config, allocAligned, nullptr));
 

+ 2 - 2
shaders/SMAA.hlsl

@@ -50,7 +50,7 @@
  *
  * The shader has three passes, chained together as follows:
  *
- *                           |input|------------------·
+ *                           |input|------------------.
  *                              v                     |
  *                    [ SMAA*EdgeDetection ]          |
  *                              v                     |
@@ -60,7 +60,7 @@
  *                              v                     |
  *                          |blendTex|                |
  *                              v                     |
- *                [ SMAANeighborhoodBlending ] <------·
+ *                [ SMAANeighborhoodBlending ] <------.
  *                              v
  *                           |output|
  *

+ 16 - 15
src/anki/gr/common/ClassAllocator.cpp → src/anki/gr/common/ClassGpuAllocator.cpp

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <anki/gr/common/ClassAllocator.h>
+#include <anki/gr/common/ClassGpuAllocator.h>
 #include <anki/util/List.h>
 #include <anki/util/BitSet.h>
 
@@ -13,11 +13,11 @@ namespace anki
 /// Max number of sub allocations (aka slots) per chunk.
 const U MAX_SLOTS_PER_CHUNK = 128;
 
-class ClassAllocatorChunk : public IntrusiveListEnabled<ClassAllocatorChunk>
+class ClassGpuAllocatorChunk : public IntrusiveListEnabled<ClassGpuAllocatorChunk>
 {
 public:
 	/// mem.
-	ClassAllocatorMemory* m_mem;
+	ClassGpuAllocatorMemory* m_mem;
 
 	/// The in use slots mask.
 	BitSet<MAX_SLOTS_PER_CHUNK, U8> m_inUseSlots = {false};
@@ -26,14 +26,14 @@ public:
 	U32 m_inUseSlotCount = 0;
 
 	/// The owner.
-	ClassAllocatorClass* m_class = nullptr;
+	ClassGpuAllocatorClass* m_class = nullptr;
 };
 
-class ClassAllocatorClass
+class ClassGpuAllocatorClass
 {
 public:
 	/// The active chunks.
-	IntrusiveList<ClassAllocatorChunk> m_inUseChunks;
+	IntrusiveList<ClassGpuAllocatorChunk> m_inUseChunks;
 
 	/// The size of each chunk.
 	PtrSize m_chunkSize = 0;
@@ -47,17 +47,18 @@ public:
 	Mutex m_mtx;
 };
 
-ClassAllocator::~ClassAllocator()
+ClassGpuAllocator::~ClassGpuAllocator()
 {
 	for(Class& c : m_classes)
 	{
+		(void)c;
 		ANKI_ASSERT(c.m_inUseChunks.isEmpty() && "Forgot to deallocate");
 	}
 
 	m_classes.destroy(m_alloc);
 }
 
-void ClassAllocator::init(GenericMemoryPoolAllocator<U8> alloc, ClassAllocatorInterface* iface)
+void ClassGpuAllocator::init(GenericMemoryPoolAllocator<U8> alloc, ClassGpuAllocatorInterface* iface)
 {
 	ANKI_ASSERT(iface);
 	m_alloc = alloc;
@@ -87,7 +88,7 @@ void ClassAllocator::init(GenericMemoryPoolAllocator<U8> alloc, ClassAllocatorIn
 	}
 }
 
-ClassAllocator::Class* ClassAllocator::findClass(PtrSize size, U alignment)
+ClassGpuAllocator::Class* ClassGpuAllocator::findClass(PtrSize size, U alignment)
 {
 	ANKI_ASSERT(size > 0 && alignment > 0);
 
@@ -129,7 +130,7 @@ ClassAllocator::Class* ClassAllocator::findClass(PtrSize size, U alignment)
 	return nullptr;
 }
 
-ClassAllocator::Chunk* ClassAllocator::findChunkWithUnusedSlot(Class& cl)
+ClassGpuAllocator::Chunk* ClassGpuAllocator::findChunkWithUnusedSlot(Class& cl)
 {
 	auto it = cl.m_inUseChunks.getBegin();
 	const auto end = cl.m_inUseChunks.getEnd();
@@ -146,9 +147,9 @@ ClassAllocator::Chunk* ClassAllocator::findChunkWithUnusedSlot(Class& cl)
 	return nullptr;
 }
 
-Error ClassAllocator::createChunk(Class& cl, Chunk*& chunk)
+Error ClassGpuAllocator::createChunk(Class& cl, Chunk*& chunk)
 {
-	ClassAllocatorMemory* mem = nullptr;
+	ClassGpuAllocatorMemory* mem = nullptr;
 
 	ANKI_CHECK(m_iface->allocate(&cl - &m_classes[0], mem));
 	ANKI_ASSERT(mem);
@@ -162,14 +163,14 @@ Error ClassAllocator::createChunk(Class& cl, Chunk*& chunk)
 	return ErrorCode::NONE;
 }
 
-void ClassAllocator::destroyChunk(Class& cl, Chunk& chunk)
+void ClassGpuAllocator::destroyChunk(Class& cl, Chunk& chunk)
 {
 	cl.m_inUseChunks.erase(&chunk);
 	m_iface->free(chunk.m_mem);
 	m_alloc.deleteInstance(&chunk);
 }
 
-Error ClassAllocator::allocate(PtrSize size, U alignment, ClassAllocatorHandle& handle)
+Error ClassGpuAllocator::allocate(PtrSize size, U alignment, ClassGpuAllocatorHandle& handle)
 {
 	ANKI_ASSERT(!handle);
 	ANKI_ASSERT(handle.valid());
@@ -209,7 +210,7 @@ Error ClassAllocator::allocate(PtrSize size, U alignment, ClassAllocatorHandle&
 	return ErrorCode::NONE;
 }
 
-void ClassAllocator::free(ClassAllocatorHandle& handle)
+void ClassGpuAllocator::free(ClassGpuAllocatorHandle& handle)
 {
 	ANKI_ASSERT(handle);
 	ANKI_ASSERT(handle.valid());

+ 22 - 22
src/anki/gr/common/ClassAllocator.h → src/anki/gr/common/ClassGpuAllocator.h

@@ -11,30 +11,30 @@ namespace anki
 {
 
 // Forward
-class ClassAllocatorChunk;
-class ClassAllocatorClass;
+class ClassGpuAllocatorChunk;
+class ClassGpuAllocatorClass;
 
 /// @addtogroup graphics
 /// @{
 
 /// The user defined output of an allocation.
-class ClassAllocatorMemory
+class ClassGpuAllocatorMemory
 {
 };
 
 /// The user defined methods to allocate memory.
-class ClassAllocatorInterface
+class ClassGpuAllocatorInterface
 {
 public:
-	virtual ~ClassAllocatorInterface()
+	virtual ~ClassGpuAllocatorInterface()
 	{
 	}
 
 	/// Allocate memory. Should be thread safe.
-	virtual ANKI_USE_RESULT Error allocate(U classIdx, ClassAllocatorMemory*& mem) = 0;
+	virtual ANKI_USE_RESULT Error allocate(U classIdx, ClassGpuAllocatorMemory*& mem) = 0;
 
 	/// Free memory. Should be thread safe.
-	virtual void free(ClassAllocatorMemory* mem) = 0;
+	virtual void free(ClassGpuAllocatorMemory* mem) = 0;
 
 	/// Get the number of classes.
 	virtual U getClassCount() const = 0;
@@ -45,12 +45,12 @@ public:
 };
 
 /// The output of an allocation.
-class ClassAllocatorHandle
+class ClassGpuAllocatorHandle
 {
-	friend class ClassAllocator;
+	friend class ClassGpuAllocator;
 
 public:
-	ClassAllocatorMemory* m_memory = nullptr;
+	ClassGpuAllocatorMemory* m_memory = nullptr;
 	PtrSize m_offset = 0;
 
 	operator Bool() const
@@ -59,7 +59,7 @@ public:
 	}
 
 private:
-	ClassAllocatorChunk* m_chunk = nullptr;
+	ClassGpuAllocatorChunk* m_chunk = nullptr;
 
 	Bool valid() const
 	{
@@ -67,7 +67,7 @@ private:
 	}
 };
 
-class ClassAllocatorStats
+class ClassGpuAllocatorStats
 {
 public:
 	PtrSize m_totalMemoryUsage;
@@ -76,31 +76,31 @@ public:
 };
 
 /// Class based allocator.
-class ClassAllocator : public NonCopyable
+class ClassGpuAllocator : public NonCopyable
 {
 public:
-	ClassAllocator()
+	ClassGpuAllocator()
 	{
 	}
 
-	~ClassAllocator();
+	~ClassGpuAllocator();
 
-	void init(GenericMemoryPoolAllocator<U8> alloc, ClassAllocatorInterface* iface);
+	void init(GenericMemoryPoolAllocator<U8> alloc, ClassGpuAllocatorInterface* iface);
 
 	/// Allocate memory.
-	ANKI_USE_RESULT Error allocate(PtrSize size, U alignment, ClassAllocatorHandle& handle);
+	ANKI_USE_RESULT Error allocate(PtrSize size, U alignment, ClassGpuAllocatorHandle& handle);
 
 	/// Free allocated memory.
-	void free(ClassAllocatorHandle& handle);
+	void free(ClassGpuAllocatorHandle& handle);
 
-	void getStats(ClassAllocatorStats& stats) const;
+	void getStats(ClassGpuAllocatorStats& stats) const;
 
 private:
-	using Class = ClassAllocatorClass;
-	using Chunk = ClassAllocatorChunk;
+	using Class = ClassGpuAllocatorClass;
+	using Chunk = ClassGpuAllocatorChunk;
 
 	GenericMemoryPoolAllocator<U8> m_alloc;
-	ClassAllocatorInterface* m_iface = nullptr;
+	ClassGpuAllocatorInterface* m_iface = nullptr;
 
 	/// The memory classes.
 	DynamicArray<Class> m_classes;

+ 0 - 18
src/anki/gr/common/LinearAllocator.h

@@ -1,18 +0,0 @@
-// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma once
-
-#include <anki/gr/Common.h>
-
-namespace anki
-{
-
-/// @addtogroup graphics
-/// @{
-
-/// @}
-
-} // end namespace anki

+ 137 - 0
src/anki/gr/common/StackGpuAllocator.cpp

@@ -0,0 +1,137 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/gr/common/StackGpuAllocator.h>
+
+namespace anki
+{
+
+class StackGpuAllocatorChunk
+{
+public:
+	StackGpuAllocatorChunk* m_next;
+	StackGpuAllocatorMemory* m_mem;
+	Atomic<U32> m_offset;
+	PtrSize m_size;
+};
+
+StackGpuAllocator::~StackGpuAllocator()
+{
+	Chunk* chunk = m_chunkListHead;
+	while(chunk)
+	{
+		if(chunk->m_mem)
+		{
+			m_iface->free(chunk->m_mem);
+		}
+
+		Chunk* next = chunk->m_next;
+		m_alloc.deleteInstance(chunk);
+		chunk = next;
+	}
+}
+
+void StackGpuAllocator::init(GenericMemoryPoolAllocator<U8> alloc, StackGpuAllocatorInterface* iface)
+{
+	ANKI_ASSERT(iface);
+	m_alloc = alloc;
+	m_iface = iface;
+	iface->getChunkGrowInfo(m_scale, m_bias, m_initialSize);
+	ANKI_ASSERT(m_scale >= 1.0);
+	ANKI_ASSERT(m_initialSize > 0);
+
+	m_alignment = iface->getMaxAlignment();
+	ANKI_ASSERT(m_alignment > 0);
+	ANKI_ASSERT(m_initialSize >= m_alignment);
+	ANKI_ASSERT((m_initialSize % m_alignment) == 0);
+}
+
+Error StackGpuAllocator::allocate(PtrSize size, StackGpuAllocatorHandle& handle)
+{
+	alignRoundUp(m_alignment, size);
+	ANKI_ASSERT(size > 0);
+	ANKI_ASSERT(size <= m_initialSize && "The chunks should have enough space to hold at least one allocation");
+
+	Chunk* crntChunk;
+	Bool retry = true;
+
+	do
+	{
+		crntChunk = m_crntChunk.load();
+		PtrSize offset;
+
+		if(crntChunk && ((offset = crntChunk->m_offset.fetchAdd(size)) + size) <= crntChunk->m_size)
+		{
+			// All is fine, there is enough space in the chunk
+
+			handle.m_memory = crntChunk->m_mem;
+			handle.m_offset = offset;
+
+			retry = false;
+		}
+		else
+		{
+			// Need new chunk
+
+			LockGuard<Mutex> lock(m_lock);
+
+			// Make sure that only one thread will create a new chunk
+			if(m_crntChunk.load() == crntChunk)
+			{
+				// We can create a new chunk
+
+				if(crntChunk == nullptr || crntChunk->m_next == nullptr)
+				{
+					// Need to create a new chunk
+
+					Chunk* newChunk = m_alloc.newInstance<Chunk>();
+
+					if(crntChunk)
+					{
+						crntChunk->m_next = newChunk;
+						newChunk->m_size = crntChunk->m_size * m_scale + m_bias;
+					}
+					else
+					{
+						newChunk->m_size = m_initialSize;
+
+						if(m_chunkListHead == nullptr)
+						{
+							m_chunkListHead = newChunk;
+						}
+					}
+					alignRoundUp(m_alignment, newChunk->m_size);
+
+					newChunk->m_next = nullptr;
+					newChunk->m_offset.set(0);
+					ANKI_CHECK(m_iface->allocate(newChunk->m_size, m_alignment, newChunk->m_mem));
+
+					m_crntChunk.store(newChunk);
+				}
+				else
+				{
+					// Need to recycle one
+
+					crntChunk->m_next->m_offset.set(0);
+
+					m_crntChunk.store(crntChunk->m_next);
+				}
+			}
+		}
+	} while(retry);
+
+	return ErrorCode::NONE;
+}
+
+void StackGpuAllocator::reset()
+{
+	m_crntChunk.set(m_chunkListHead);
+	if(m_chunkListHead)
+	{
+		m_chunkListHead->m_offset.set(0);
+	}
+}
+
+} // end namespace anki

+ 102 - 0
src/anki/gr/common/StackGpuAllocator.h

@@ -0,0 +1,102 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/gr/Common.h>
+
+namespace anki
+{
+
+// Forward
+class StackGpuAllocatorChunk;
+
+/// @addtogroup graphics
+/// @{
+
+/// The user defined output of an allocation.
+class StackGpuAllocatorMemory
+{
+};
+
+/// The user defined methods to allocate memory.
+class StackGpuAllocatorInterface
+{
+public:
+	virtual ~StackGpuAllocatorInterface()
+	{
+	}
+
+	/// Allocate memory. Should be thread safe.
+	virtual ANKI_USE_RESULT Error allocate(PtrSize size, U32 alignment, StackGpuAllocatorMemory*& mem) = 0;
+
+	/// Free memory. Should be thread safe.
+	virtual void free(StackGpuAllocatorMemory* mem) = 0;
+
+	/// If the current chunk of the linear allocator is of size N then the next chunk will be of size N*scale+bias.
+	virtual void getChunkGrowInfo(F32& scale, PtrSize& bias, PtrSize& initialSize) = 0;
+
+	virtual U32 getMaxAlignment() = 0;
+};
+
+/// The output of an allocation.
+class StackGpuAllocatorHandle
+{
+	friend class StackGpuAllocator;
+
+public:
+	StackGpuAllocatorMemory* m_memory = nullptr;
+	PtrSize m_offset = 0;
+
+	operator Bool() const
+	{
+		return m_memory != nullptr;
+	}
+
+private:
+	StackGpuAllocatorChunk* m_chunk = nullptr;
+
+	Bool valid() const
+	{
+		return (m_memory && m_chunk) || (m_memory == nullptr && m_chunk == nullptr);
+	}
+};
+
+/// Linear based allocator.
+class StackGpuAllocator : public NonCopyable
+{
+public:
+	StackGpuAllocator()
+	{
+	}
+
+	~StackGpuAllocator();
+
+	void init(GenericMemoryPoolAllocator<U8> alloc, StackGpuAllocatorInterface* iface);
+
+	/// Allocate memory.
+	ANKI_USE_RESULT Error allocate(PtrSize size, StackGpuAllocatorHandle& handle);
+
+	/// Reset all the memory chunks. Not thread safe.
+	void reset();
+
+private:
+	using Chunk = StackGpuAllocatorChunk;
+
+	GenericMemoryPoolAllocator<U8> m_alloc;
+	StackGpuAllocatorInterface* m_iface = nullptr;
+
+	Chunk* m_chunkListHead = nullptr;
+	Atomic<Chunk*> m_crntChunk = {nullptr};
+	Mutex m_lock;
+
+	F32 m_scale = 0.0;
+	PtrSize m_bias = 0;
+	PtrSize m_initialSize = 0;
+	U32 m_alignment = 0;
+};
+/// @}
+
+} // end namespace anki

+ 7 - 5
src/anki/renderer/Drawer.cpp

@@ -341,12 +341,12 @@ Error RenderableDrawer::drawRange(Pass pass,
 
 	// Flush the last drawcall
 	CompleteRenderingBuildInfo& build = ctx.m_buildInfo[!ctx.m_crntBuildInfo];
-	flushDrawcall(ctx, build);
+	ANKI_CHECK(flushDrawcall(ctx, build));
 
 	return ErrorCode::NONE;
 }
 
-void RenderableDrawer::flushDrawcall(DrawContext& ctx, CompleteRenderingBuildInfo& build)
+Error RenderableDrawer::flushDrawcall(DrawContext& ctx, CompleteRenderingBuildInfo& build)
 {
 	RenderComponent& rc = *build.m_rc;
 
@@ -354,7 +354,7 @@ void RenderableDrawer::flushDrawcall(DrawContext& ctx, CompleteRenderingBuildInf
 	if(ctx.m_cachedTrfCount > 1)
 	{
 		build.m_in.m_key.m_instanceCount = ctx.m_cachedTrfCount;
-		rc.buildRendering(build.m_in, build.m_out);
+		ANKI_CHECK(rc.buildRendering(build.m_in, build.m_out));
 	}
 
 	// Create the pipeline
@@ -394,6 +394,8 @@ void RenderableDrawer::flushDrawcall(DrawContext& ctx, CompleteRenderingBuildInf
 		ANKI_TRACE_INC_COUNTER(RENDERER_MERGED_DRAWCALLS, ctx.m_cachedTrfCount - 1);
 	}
 	ctx.m_cachedTrfCount = 0;
+
+	return ErrorCode::NONE;
 }
 
 Error RenderableDrawer::drawSingle(DrawContext& ctx)
@@ -423,7 +425,7 @@ Error RenderableDrawer::drawSingle(DrawContext& ctx)
 	crntBuild.m_in.m_subMeshIndicesCount = ctx.m_visibleNode->m_spatialsCount;
 
 	resetRenderingBuildInfoOut(crntBuild.m_out);
-	renderable.buildRendering(crntBuild.m_in, crntBuild.m_out);
+	ANKI_CHECK(renderable.buildRendering(crntBuild.m_in, crntBuild.m_out));
 	ANKI_ASSERT(crntBuild.m_out.m_stateMask
 		== (PipelineSubStateBit::VERTEX | PipelineSubStateBit::INPUT_ASSEMBLER | PipelineSubStateBit::SHADERS));
 
@@ -447,7 +449,7 @@ Error RenderableDrawer::drawSingle(DrawContext& ctx)
 	{
 		// Cannot merge, flush the previous build info
 
-		flushDrawcall(ctx, prevBuild);
+		ANKI_CHECK(flushDrawcall(ctx, prevBuild));
 
 		// Cache the current build
 		if(crntBuild.m_out.m_hasTransform)

+ 1 - 1
src/anki/renderer/Drawer.h

@@ -45,7 +45,7 @@ public:
 private:
 	Renderer* m_r;
 
-	void flushDrawcall(DrawContext& ctx, CompleteRenderingBuildInfo& build);
+	ANKI_USE_RESULT Error flushDrawcall(DrawContext& ctx, CompleteRenderingBuildInfo& build);
 	void setupUniforms(DrawContext& ctx, CompleteRenderingBuildInfo& build);
 
 	ANKI_USE_RESULT Error drawSingle(DrawContext& ctx);

+ 12 - 2
src/anki/util/Atomic.h

@@ -7,6 +7,7 @@
 
 #include <anki/util/StdTypes.h>
 #include <anki/util/NonCopyable.h>
+#include <type_traits>
 
 namespace anki
 {
@@ -39,6 +40,7 @@ public:
 	/// It will not set itself to zero.
 	Atomic()
 	{
+		static_assert(std::is_pointer<T>::value || std::is_arithmetic<T>::value, "Check the type");
 	}
 
 	Atomic(const Value& a)
@@ -83,7 +85,11 @@ public:
 	Value fetchAdd(const Y& a, AtomicMemoryOrder memOrd = MEMORY_ORDER)
 	{
 #if defined(__GNUC__)
-		return __atomic_fetch_add(&m_val, a, static_cast<int>(memOrd));
+		// Do a trick to workaround the fact that __atomic_fetch_add doesn't take into account the size of the type
+		// if that type is a pointer.
+		Value v = Value(0);
+		v += a;
+		return __atomic_fetch_add(&m_val, v, static_cast<int>(memOrd));
 #else
 #error "TODO"
 #endif
@@ -94,7 +100,11 @@ public:
 	Value fetchSub(const Y& a, AtomicMemoryOrder memOrd = MEMORY_ORDER)
 	{
 #if defined(__GNUC__)
-		return __atomic_fetch_sub(&m_val, a, static_cast<int>(memOrd));
+		// Do a trick to workaround the fact that __atomic_fetch_add doesn't take into account the size of the type
+		// if that type is a pointer.
+		Value v = Value(0);
+		v -= a;
+		return __atomic_fetch_sub(&m_val, v, static_cast<int>(memOrd));
 #else
 #error "TODO"
 #endif

+ 1 - 2
src/anki/util/Memory.cpp

@@ -344,8 +344,7 @@ void* StackMemoryPool::allocate(PtrSize size, PtrSize alignment)
 
 	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");
+	ANKI_ASSERT(size <= m_initialChunkSize && "The chunks should have enough space to hold at least one allocation");
 
 	Chunk* crntChunk = nullptr;
 	Bool retry = true;

+ 11 - 11
tests/gr/ClassAllocator.cpp → tests/gr/ClassGpuAllocator.cpp

@@ -3,7 +3,7 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <anki/gr/common/ClassAllocator.h>
+#include <anki/gr/common/ClassGpuAllocator.h>
 #include <tests/framework/Framework.h>
 #include <random>
 #include <algorithm>
@@ -11,14 +11,14 @@
 namespace anki
 {
 
-class Mem : public ClassAllocatorMemory
+class Mem : public ClassGpuAllocatorMemory
 {
 public:
 	void* m_mem = nullptr;
 	PtrSize m_size = 0;
 };
 
-class Interface final : public ClassAllocatorInterface
+class Interface final : public ClassGpuAllocatorInterface
 {
 public:
 	class Class
@@ -49,7 +49,7 @@ public:
 		m_classes.push_back(Class(128 * 1024 * 1024, 256 * 1024 * 1024));
 	}
 
-	ANKI_USE_RESULT Error allocate(U classIdx, ClassAllocatorMemory*& mem)
+	ANKI_USE_RESULT Error allocate(U classIdx, ClassGpuAllocatorMemory*& mem)
 	{
 		PtrSize size = m_classes[classIdx].m_clusterSize;
 
@@ -71,7 +71,7 @@ public:
 		return ErrorCode::NONE;
 	}
 
-	void free(ClassAllocatorMemory* mem)
+	void free(ClassGpuAllocatorMemory* mem)
 	{
 		Mem* m = static_cast<Mem*>(mem);
 		m_crntSize -= m->m_size;
@@ -103,12 +103,12 @@ static inline U32 floorPow2(U32 v)
 	return v >> 1;
 }
 
-ANKI_TEST(Gr, ClassAllocator)
+ANKI_TEST(Gr, ClassGpuAllocator)
 {
 	HeapAllocator<U8> alloc(allocAligned, nullptr);
 	Interface iface;
 
-	ClassAllocator calloc;
+	ClassGpuAllocator calloc;
 	calloc.init(alloc, &iface);
 
 	std::mt19937 gen(0);
@@ -121,7 +121,7 @@ ANKI_TEST(Gr, ClassAllocator)
 		return size;
 	};
 
-	std::vector<ClassAllocatorHandle> handles;
+	std::vector<ClassGpuAllocatorHandle> handles;
 	const U TEST_COUNT = 100;
 	const U ITERATIONS = 20;
 
@@ -132,7 +132,7 @@ ANKI_TEST(Gr, ClassAllocator)
 			// Fill up the heap.
 			while(1)
 			{
-				ClassAllocatorHandle handle;
+				ClassGpuAllocatorHandle handle;
 				PtrSize size = nextAllocSize();
 
 				if(calloc.allocate(size, 1, handle))
@@ -180,7 +180,7 @@ ANKI_TEST(Gr, ClassAllocator)
 
 		while(baseFreeSize >= BASE_SIZE)
 		{
-			ClassAllocatorHandle handle;
+			ClassGpuAllocatorHandle handle;
 			while(calloc.allocate(baseFreeSize, 1, handle) == ErrorCode::NONE)
 			{
 				score += (pow(POWER, (log2(F32(baseFreeSize / BASE_SIZE)) + BIAS)) + OFFSET) * baseFreeSize;
@@ -194,7 +194,7 @@ ANKI_TEST(Gr, ClassAllocator)
 		printf("Score: %.3f\n", score / bestCase);
 
 		// Cleanup
-		for(ClassAllocatorHandle& h : handles)
+		for(ClassGpuAllocatorHandle& h : handles)
 		{
 			calloc.free(h);
 		}

+ 2 - 2
src/anki/gr/common/LinearAllocator.cpp → tests/gr/StackGpuAllocator.cpp

@@ -3,5 +3,5 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include <anki/gr/common/LinearAllocator.h>
-
+#include <anki/gr/common/StackGpuAllocator.h>
+#include <tests/framework/Framework.h>

+ 48 - 0
tests/util/Atomic.cpp

@@ -0,0 +1,48 @@
+// Copyright (C) 2009-2016, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include "tests/framework/Framework.h"
+#include "anki/util/Atomic.h"
+
+ANKI_TEST(Util, Atomic)
+{
+	{
+		Atomic<I32> a{MAX_I32};
+		I32 b = MAX_I32;
+		a.fetchAdd(1);
+		++b;
+		ANKI_TEST_EXPECT_EQ(a.load(), b);
+	}
+
+	{
+		Atomic<I32> a{MAX_I32};
+		I32 b = MAX_I32;
+		a.fetchAdd(-1);
+		--b;
+		ANKI_TEST_EXPECT_EQ(a.load(), b);
+	}
+
+	{
+		Atomic<I32> a{MAX_I32};
+		I32 b = MAX_I32;
+		a.fetchSub(1);
+		--b;
+		ANKI_TEST_EXPECT_EQ(a.load(), b);
+	}
+
+	{
+		U32 u[2];
+		Atomic<U32*> a{&u[0]};
+		a.fetchAdd(1);
+		ANKI_TEST_EXPECT_EQ(a.load(), &u[1]);
+	}
+
+	{
+		U32 u[2];
+		Atomic<U32*> a{&u[1]};
+		a.fetchSub(1);
+		ANKI_TEST_EXPECT_EQ(a.load(), &u[0]);
+	}
+}