Prechádzať zdrojové kódy

Vulkan: Refactor command buffer creation

Panagiotis Christopoulos Charitos 8 rokov pred
rodič
commit
758c1c2e08

+ 0 - 107
src/anki/gr/vulkan/CommandBufferExtra.cpp

@@ -1,107 +0,0 @@
-// Copyright (C) 2009-2017, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#include <anki/gr/vulkan/CommandBufferExtra.h>
-#include <anki/core/Trace.h>
-
-namespace anki
-{
-
-CommandBufferFactory::~CommandBufferFactory()
-{
-	for(U i = 0; i < 2; ++i)
-	{
-		for(U j = 0; j < 2; ++j)
-		{
-			CmdbType& type = m_types[i][j];
-
-			if(type.m_count)
-			{
-				vkFreeCommandBuffers(m_dev, m_pool, type.m_count, &type.m_cmdbs[0]);
-			}
-
-			type.m_cmdbs.destroy(m_alloc);
-		}
-	}
-
-	if(m_pool)
-	{
-		vkDestroyCommandPool(m_dev, m_pool, nullptr);
-	}
-}
-
-Error CommandBufferFactory::init(GenericMemoryPoolAllocator<U8> alloc, VkDevice dev, uint32_t queueFamily)
-{
-	m_alloc = alloc;
-
-	VkCommandPoolCreateInfo ci = {};
-	ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
-	ci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
-	ci.queueFamilyIndex = queueFamily;
-
-	ANKI_VK_CHECK(vkCreateCommandPool(dev, &ci, nullptr, &m_pool));
-
-	m_dev = dev;
-	return ErrorCode::NONE;
-}
-
-VkCommandBuffer CommandBufferFactory::newCommandBuffer(CommandBufferFlag cmdbFlags)
-{
-	ANKI_ASSERT(isCreated());
-
-	Bool secondLevel = !!(cmdbFlags & CommandBufferFlag::SECOND_LEVEL);
-	Bool smallBatch = !!(cmdbFlags & CommandBufferFlag::SMALL_BATCH);
-	CmdbType& type = m_types[secondLevel][smallBatch];
-
-	LockGuard<Mutex> lock(type.m_mtx);
-
-	VkCommandBuffer out = VK_NULL_HANDLE;
-	if(type.m_count > 0)
-	{
-		// Recycle
-
-		--type.m_count;
-		out = type.m_cmdbs[type.m_count];
-	}
-	else
-	{
-		// Create a new one
-
-		VkCommandBufferAllocateInfo ci = {};
-		ci.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
-		ci.commandPool = m_pool;
-		ci.level = (secondLevel) ? VK_COMMAND_BUFFER_LEVEL_SECONDARY : VK_COMMAND_BUFFER_LEVEL_PRIMARY;
-		ci.commandBufferCount = 1;
-
-		ANKI_TRACE_INC_COUNTER(VK_CMD_BUFFER_CREATE, 1);
-		ANKI_VK_CHECKF(vkAllocateCommandBuffers(m_dev, &ci, &out));
-	}
-
-	ANKI_ASSERT(out);
-	return out;
-}
-
-void CommandBufferFactory::deleteCommandBuffer(VkCommandBuffer cmdb, CommandBufferFlag cmdbFlags)
-{
-	ANKI_ASSERT(isCreated());
-	ANKI_ASSERT(cmdb);
-
-	Bool secondLevel = !!(cmdbFlags & CommandBufferFlag::SECOND_LEVEL);
-	Bool smallBatch = !!(cmdbFlags & CommandBufferFlag::SMALL_BATCH);
-	CmdbType& type = m_types[secondLevel][smallBatch];
-
-	LockGuard<Mutex> lock(type.m_mtx);
-
-	if(type.m_cmdbs.getSize() <= type.m_count)
-	{
-		// Grow storage
-		type.m_cmdbs.resize(m_alloc, type.m_cmdbs.getSize() + 1);
-	}
-
-	type.m_cmdbs[type.m_count] = cmdb;
-	++type.m_count;
-}
-
-} // end namespace anki

+ 0 - 59
src/anki/gr/vulkan/CommandBufferExtra.h

@@ -1,59 +0,0 @@
-// Copyright (C) 2009-2017, Panagiotis Christopoulos Charitos and contributors.
-// All rights reserved.
-// Code licensed under the BSD License.
-// http://www.anki3d.org/LICENSE
-
-#pragma once
-
-#include <anki/gr/vulkan/Common.h>
-#include <anki/gr/CommandBuffer.h>
-
-namespace anki
-{
-
-/// @addtogroup vulkan
-/// @{
-
-/// Command bufffer object recycler.
-class CommandBufferFactory
-{
-public:
-	CommandBufferFactory()
-	{
-	}
-
-	~CommandBufferFactory();
-
-	ANKI_USE_RESULT Error init(GenericMemoryPoolAllocator<U8> alloc, VkDevice dev, uint32_t queueFamily);
-
-	/// Request a new command buffer.
-	VkCommandBuffer newCommandBuffer(CommandBufferFlag cmdbFlags);
-
-	/// Free a command buffer.
-	void deleteCommandBuffer(VkCommandBuffer cmdb, CommandBufferFlag cmdbFlags);
-
-	void collect();
-
-	Bool isCreated() const
-	{
-		return m_dev != VK_NULL_HANDLE;
-	}
-
-private:
-	GenericMemoryPoolAllocator<U8> m_alloc;
-	VkDevice m_dev = VK_NULL_HANDLE;
-	VkCommandPool m_pool = VK_NULL_HANDLE;
-
-	class CmdbType
-	{
-	public:
-		DynamicArray<VkCommandBuffer> m_cmdbs; ///< A stack.
-		U32 m_count = 0;
-		Mutex m_mtx; ///< Lock because the allocations may happen anywhere.
-	};
-
-	Array2d<CmdbType, 2, 2> m_types;
-};
-/// @}
-
-} // end namespace anki

+ 278 - 0
src/anki/gr/vulkan/CommandBufferFactory.cpp

@@ -0,0 +1,278 @@
+// Copyright (C) 2009-2017, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/gr/vulkan/CommandBufferFactory.h>
+#include <anki/core/Trace.h>
+
+namespace anki
+{
+
+void MicroCommandBuffer::destroy()
+{
+	reset();
+
+	if(m_handle)
+	{
+		vkFreeCommandBuffers(m_threadAlloc->m_factory->m_dev, m_threadAlloc->m_pool, 1, &m_handle);
+		m_handle = {};
+	}
+}
+
+void MicroCommandBuffer::reset()
+{
+	ANKI_ASSERT(m_refcount.load() == 0);
+	ANKI_ASSERT(!m_fence.isCreated() || m_fence->done());
+
+	m_objectRefs.destroy(m_fastAlloc);
+	m_objectRefCount = 0;
+
+	m_fastAlloc.getMemoryPool().reset();
+
+	m_fence = {};
+}
+
+Error CommandBufferThreadAllocator::init()
+{
+	VkCommandPoolCreateInfo ci = {};
+	ci.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
+	ci.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
+	ci.queueFamilyIndex = m_factory->m_queueFamily;
+
+	ANKI_VK_CHECK(vkCreateCommandPool(m_factory->m_dev, &ci, nullptr, &m_pool));
+
+	return ErrorCode::NONE;
+}
+
+void CommandBufferThreadAllocator::destroyList(IntrusiveList<MicroCommandBuffer>& list)
+{
+	while(!list.isEmpty())
+	{
+		MicroCommandBuffer* ptr = &list.getFront();
+		list.popFront();
+		ptr->destroy();
+		getAllocator().deleteInstance(ptr);
+	}
+}
+
+void CommandBufferThreadAllocator::destroy()
+{
+	for(U i = 0; i < 2; ++i)
+	{
+		for(U j = 0; j < 2; ++j)
+		{
+			CmdbType& type = m_types[i][j];
+
+			destroyList(type.m_readyCmdbs);
+			destroyList(type.m_inUseCmdbs);
+			destroyList(type.m_deletedCmdbs);
+		}
+	}
+
+	if(m_pool)
+	{
+		vkDestroyCommandPool(m_factory->m_dev, m_pool, nullptr);
+	}
+}
+
+Error CommandBufferThreadAllocator::newCommandBuffer(CommandBufferFlag cmdbFlags, MicroCommandBufferPtr& outPtr)
+{
+	cmdbFlags = cmdbFlags & (CommandBufferFlag::SECOND_LEVEL | CommandBufferFlag::SMALL_BATCH);
+
+	Bool secondLevel = !!(cmdbFlags & CommandBufferFlag::SECOND_LEVEL);
+	Bool smallBatch = !!(cmdbFlags & CommandBufferFlag::SMALL_BATCH);
+	CmdbType& type = m_types[secondLevel][smallBatch];
+
+	// Move the deleted to (possibly) in-use
+	{
+		LockGuard<Mutex> lock(type.m_deletedMtx);
+
+		while(!type.m_deletedCmdbs.isEmpty())
+		{
+			MicroCommandBuffer* ptr = &type.m_deletedCmdbs.getFront();
+			type.m_deletedCmdbs.popFront();
+
+			if(secondLevel)
+			{
+				type.m_readyCmdbs.pushBack(ptr);
+			}
+			else
+			{
+				type.m_inUseCmdbs.pushBack(ptr);
+			}
+		}
+	}
+
+	// Reset the in-use command buffers and try to get one available
+	MicroCommandBuffer* out = nullptr;
+	if(!secondLevel)
+	{
+		// Primary
+
+		IntrusiveList<MicroCommandBuffer> inUseCmdbs; // Push to temporary
+
+		while(!type.m_inUseCmdbs.isEmpty())
+		{
+			MicroCommandBuffer* mcmdb = &type.m_inUseCmdbs.getFront();
+			type.m_inUseCmdbs.popFront();
+
+			if(!mcmdb->m_fence.isCreated() || mcmdb->m_fence->done())
+			{
+				// Can re-use it
+				if(out)
+				{
+					type.m_readyCmdbs.pushBack(mcmdb);
+				}
+				else
+				{
+					out = mcmdb;
+				}
+
+				mcmdb->reset();
+			}
+			else
+			{
+				inUseCmdbs.pushBack(mcmdb);
+			}
+		}
+
+		ANKI_ASSERT(type.m_inUseCmdbs.isEmpty());
+		type.m_inUseCmdbs = std::move(inUseCmdbs);
+	}
+	else
+	{
+		ANKI_ASSERT(type.m_inUseCmdbs.isEmpty());
+
+		if(!type.m_readyCmdbs.isEmpty())
+		{
+			out = &type.m_readyCmdbs.getFront();
+			type.m_readyCmdbs.popFront();
+		}
+	}
+
+	if(ANKI_UNLIKELY(out == nullptr))
+	{
+		// Create a new one
+
+		VkCommandBufferAllocateInfo ci = {};
+		ci.sType = VK_STRUCTURE_TYPE_COMMAND_BUFFER_ALLOCATE_INFO;
+		ci.commandPool = m_pool;
+		ci.level = (secondLevel) ? VK_COMMAND_BUFFER_LEVEL_SECONDARY : VK_COMMAND_BUFFER_LEVEL_PRIMARY;
+		ci.commandBufferCount = 1;
+
+		ANKI_TRACE_INC_COUNTER(VK_CMD_BUFFER_CREATE, 1);
+		VkCommandBuffer cmdb;
+		ANKI_VK_CHECK(vkAllocateCommandBuffers(m_factory->m_dev, &ci, &cmdb));
+
+		MicroCommandBuffer* newCmdb = getAllocator().newInstance<MicroCommandBuffer>(this);
+		newCmdb->m_fastAlloc = StackAllocator<U8>(m_factory->m_alloc.getMemoryPool().getAllocationCallback(),
+			m_factory->m_alloc.getMemoryPool().getAllocationCallbackUserData(),
+			(smallBatch) ? (1 * 1024) : (10 * 1024),
+			2.0f);
+
+		newCmdb->m_handle = cmdb;
+		newCmdb->m_flags = cmdbFlags;
+
+		out = newCmdb;
+	}
+
+	ANKI_ASSERT(out && out->m_refcount.load() == 0);
+	outPtr.reset(out);
+	return ErrorCode::NONE;
+}
+
+void CommandBufferThreadAllocator::deleteCommandBuffer(MicroCommandBuffer* ptr)
+{
+	ANKI_ASSERT(ptr);
+
+	Bool secondLevel = !!(ptr->m_flags & CommandBufferFlag::SECOND_LEVEL);
+	Bool smallBatch = !!(ptr->m_flags & CommandBufferFlag::SMALL_BATCH);
+
+	if(secondLevel)
+	{
+		// We can safely reset the 2nd level cmdbs early
+		ptr->reset();
+	}
+
+	CmdbType& type = m_types[secondLevel][smallBatch];
+
+	LockGuard<Mutex> lock(type.m_deletedMtx);
+	type.m_deletedCmdbs.pushBack(ptr);
+}
+
+Error CommandBufferFactory::init(GrAllocator<U8> alloc, VkDevice dev, uint32_t queueFamily)
+{
+	ANKI_ASSERT(dev);
+
+	m_alloc = alloc;
+	m_dev = dev;
+	m_queueFamily = queueFamily;
+	return ErrorCode::NONE;
+}
+
+void CommandBufferFactory::destroy()
+{
+	for(CommandBufferThreadAllocator* alloc : m_threadAllocs)
+	{
+		alloc->destroy();
+		m_alloc.deleteInstance(alloc);
+	}
+
+	m_threadAllocs.destroy(m_alloc);
+}
+
+Error CommandBufferFactory::newCommandBuffer(ThreadId tid, CommandBufferFlag cmdbFlags, MicroCommandBufferPtr& ptr)
+{
+	CommandBufferThreadAllocator* alloc = nullptr;
+
+	{
+		LockGuard<SpinLock> lock(m_threadAllocMtx);
+
+		class Comp
+		{
+		public:
+			Bool operator()(const CommandBufferThreadAllocator* a, ThreadId tid) const
+			{
+				return a->m_tid < tid;
+			}
+
+			Bool operator()(ThreadId tid, const CommandBufferThreadAllocator* a) const
+			{
+				return tid < a->m_tid;
+			}
+		};
+
+		// Find using binary search
+		auto it = binarySearch(m_threadAllocs.getBegin(), m_threadAllocs.getEnd(), tid, Comp());
+
+		if(it != m_threadAllocs.getEnd())
+		{
+			ANKI_ASSERT((*it)->m_tid == tid);
+			alloc = *it;
+		}
+		else
+		{
+			alloc = m_alloc.newInstance<CommandBufferThreadAllocator>(this, tid);
+
+			m_threadAllocs.resize(m_alloc, m_threadAllocs.getSize() + 1);
+			m_threadAllocs[m_threadAllocs.getSize() - 1] = alloc;
+
+			// Sort for fast find
+			std::sort(m_threadAllocs.getBegin(),
+				m_threadAllocs.getEnd(),
+				[](const CommandBufferThreadAllocator* a, const CommandBufferThreadAllocator* b) {
+					return a->m_tid < b->m_tid;
+				});
+
+			ANKI_CHECK(alloc->init());
+		}
+	}
+
+	ANKI_ASSERT(alloc);
+	ANKI_CHECK(alloc->newCommandBuffer(cmdbFlags, ptr));
+
+	return ErrorCode::NONE;
+}
+
+} // end namespace anki

+ 177 - 0
src/anki/gr/vulkan/CommandBufferFactory.h

@@ -0,0 +1,177 @@
+// Copyright (C) 2009-2017, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <anki/gr/vulkan/Fence.h>
+#include <anki/gr/CommandBuffer.h>
+#include <anki/util/List.h>
+
+namespace anki
+{
+
+// Forward
+class CommandBufferFactory;
+class CommandBufferThreadAllocator;
+
+/// @addtogroup vulkan
+/// @{
+
+class MicroCommandBuffer : public IntrusiveListEnabled<MicroCommandBuffer>
+{
+	friend class CommandBufferThreadAllocator;
+	friend class MicroCommandBufferPtrDeleter;
+
+public:
+	MicroCommandBuffer(CommandBufferThreadAllocator* allocator)
+		: m_threadAlloc(allocator)
+	{
+		ANKI_ASSERT(allocator);
+	}
+
+	Atomic<I32>& getRefcount()
+	{
+		return m_refcount;
+	}
+
+	GrAllocator<U8>& getAllocator();
+
+	StackAllocator<U8>& getFastAllocator()
+	{
+		return m_fastAlloc;
+	}
+
+	VkCommandBuffer getHandle() const
+	{
+		ANKI_ASSERT(m_handle);
+		return m_handle;
+	}
+
+	template<typename T>
+	void pushObjectRef(T& x)
+	{
+		if(m_objectRefs.getSize() <= m_objectRefCount)
+		{
+			// Grow storage
+			m_objectRefs.resize(m_fastAlloc, max<PtrSize>(10, m_objectRefs.getSize() * 2));
+		}
+		GrObject* grobj = x.get();
+		m_objectRefs[m_objectRefCount++] = IntrusivePtr<GrObject>(grobj);
+	}
+
+	void setFence(FencePtr& fence)
+	{
+		ANKI_ASSERT(!(m_flags & CommandBufferFlag::SECOND_LEVEL));
+		ANKI_ASSERT(!m_fence.isCreated());
+		m_fence = fence;
+	}
+
+private:
+	CommandBufferThreadAllocator* m_threadAlloc;
+	Atomic<I32> m_refcount = {0};
+	StackAllocator<U8> m_fastAlloc;
+	VkCommandBuffer m_handle = {};
+
+	DynamicArray<IntrusivePtr<GrObject>> m_objectRefs;
+	U32 m_objectRefCount = 0;
+	CommandBufferFlag m_flags = CommandBufferFlag::NONE;
+
+	FencePtr m_fence;
+
+	void destroy();
+	void reset();
+};
+
+/// Deleter.
+class MicroCommandBufferPtrDeleter
+{
+public:
+	void operator()(MicroCommandBuffer* buff);
+};
+
+/// Micro command buffer pointer.
+using MicroCommandBufferPtr = IntrusivePtr<MicroCommandBuffer, MicroCommandBufferPtrDeleter>;
+
+/// Per-thread command buffer allocator.
+class CommandBufferThreadAllocator
+{
+	friend class CommandBufferFactory;
+	friend class MicroCommandBuffer;
+
+public:
+	CommandBufferThreadAllocator(CommandBufferFactory* factory, ThreadId tid)
+		: m_factory(factory)
+		, m_tid(tid)
+	{
+		ANKI_ASSERT(factory);
+	}
+
+	~CommandBufferThreadAllocator()
+	{
+	}
+
+	ANKI_USE_RESULT Error init();
+
+	void destroy();
+
+	GrAllocator<U8>& getAllocator();
+
+	/// Request a new command buffer.
+	ANKI_USE_RESULT Error newCommandBuffer(CommandBufferFlag cmdbFlags, MicroCommandBufferPtr& ptr);
+
+	/// It will recycle it.
+	void deleteCommandBuffer(MicroCommandBuffer* ptr);
+
+private:
+	CommandBufferFactory* m_factory;
+	ThreadId m_tid;
+	VkCommandPool m_pool = VK_NULL_HANDLE;
+
+	class CmdbType
+	{
+	public:
+		IntrusiveList<MicroCommandBuffer> m_readyCmdbs;
+		IntrusiveList<MicroCommandBuffer> m_inUseCmdbs;
+
+		IntrusiveList<MicroCommandBuffer> m_deletedCmdbs;
+		Mutex m_deletedMtx; ///< Lock because the dallocations may happen anywhere.
+	};
+
+	Array2d<CmdbType, 2, 2> m_types;
+
+	void destroyList(IntrusiveList<MicroCommandBuffer>& list);
+};
+
+/// Command bufffer object recycler.
+class CommandBufferFactory : public NonCopyable
+{
+	friend class CommandBufferThreadAllocator;
+	friend class MicroCommandBuffer;
+
+public:
+	CommandBufferFactory() = default;
+
+	~CommandBufferFactory() = default;
+
+	ANKI_USE_RESULT Error init(GrAllocator<U8> alloc, VkDevice dev, uint32_t queueFamily);
+
+	void destroy();
+
+	/// Request a new command buffer.
+	ANKI_USE_RESULT Error newCommandBuffer(ThreadId tid, CommandBufferFlag cmdbFlags, MicroCommandBufferPtr& ptr);
+
+private:
+	GrAllocator<U8> m_alloc;
+	VkDevice m_dev = VK_NULL_HANDLE;
+	uint32_t m_queueFamily;
+
+	DynamicArray<CommandBufferThreadAllocator*> m_threadAllocs;
+	SpinLock m_threadAllocMtx;
+};
+/// @}
+
+} // end namespace anki
+
+#include <anki/gr/vulkan/CommandBufferFactory.inl.h>

+ 27 - 0
src/anki/gr/vulkan/CommandBufferFactory.inl.h

@@ -0,0 +1,27 @@
+// Copyright (C) 2009-2017, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <anki/gr/vulkan/CommandBufferFactory.h>
+
+namespace anki
+{
+
+inline GrAllocator<U8>& MicroCommandBuffer::getAllocator()
+{
+	return m_threadAlloc->getAllocator();
+}
+
+inline void MicroCommandBufferPtrDeleter::operator()(MicroCommandBuffer* ptr)
+{
+	ANKI_ASSERT(ptr);
+	ptr->m_threadAlloc->deleteCommandBuffer(ptr);
+}
+
+inline GrAllocator<U8>& CommandBufferThreadAllocator::getAllocator()
+{
+	return m_factory->m_alloc;
+}
+
+} // end namespace anki

+ 30 - 26
src/anki/gr/vulkan/CommandBufferImpl.cpp

@@ -31,19 +31,23 @@ CommandBufferImpl::~CommandBufferImpl()
 	{
 		ANKI_VK_LOGW("Command buffer was not flushed");
 	}
-
-	if(m_handle)
+	
+#if ANKI_EXTRA_CHECKS
+	if(!!(m_flags & CommandBufferFlag::SMALL_BATCH))
 	{
-		getGrManagerImpl().deleteCommandBuffer(m_handle, m_flags, m_tid);
+		if(m_commandCount > 20)
+		{
+			ANKI_VK_LOGW("Command buffer has too many commands");
+		}
 	}
-
-	m_fbList.destroy(m_alloc);
-	m_texList.destroy(m_alloc);
-	m_queryList.destroy(m_alloc);
-	m_bufferList.destroy(m_alloc);
-	m_cmdbList.destroy(m_alloc);
-	m_progs.destroy(m_alloc);
-	m_samplerList.destroy(m_alloc);
+	else
+	{
+		if(m_commandCount <= 20)
+		{
+			ANKI_VK_LOGW("Command buffer has too few commands");
+		}
+	}
+#endif
 
 	m_imgBarriers.destroy(m_alloc);
 	m_buffBarriers.destroy(m_alloc);
@@ -54,16 +58,16 @@ CommandBufferImpl::~CommandBufferImpl()
 
 Error CommandBufferImpl::init(const CommandBufferInitInfo& init)
 {
-	auto& pool = getGrManagerImpl().getAllocator().getMemoryPool();
-	m_alloc = StackAllocator<U8>(
-		pool.getAllocationCallback(), pool.getAllocationCallbackUserData(), init.m_hints.m_chunkSize, 1.0, 0, false);
-
-	m_texUsageTracker.init(m_alloc);
-
 	m_flags = init.m_flags;
 	m_tid = Thread::getCurrentThreadId();
 
-	m_handle = getGrManagerImpl().newCommandBuffer(m_tid, m_flags);
+	ANKI_CHECK(getGrManagerImpl().getCommandBufferFactory().newCommandBuffer(m_tid, m_flags, m_microCmdb));
+
+	m_alloc = m_microCmdb->getFastAllocator();
+
+	m_texUsageTracker.init(m_alloc);
+
+	m_handle = m_microCmdb->getHandle();
 	ANKI_ASSERT(m_handle);
 
 	if(!!(m_flags & CommandBufferFlag::SECOND_LEVEL))
@@ -130,7 +134,7 @@ void CommandBufferImpl::beginRenderPass(FramebufferPtr fb)
 	m_rpCommandCount = 0;
 	m_activeFb = fb;
 
-	m_fbList.pushBack(m_alloc, fb);
+	m_microCmdb->pushObjectRef(fb);
 
 	m_subpassContents = VK_SUBPASS_CONTENTS_MAX_ENUM;
 }
@@ -329,7 +333,7 @@ void CommandBufferImpl::generateMipmaps2d(TexturePtr tex, U face, U layer)
 	}
 
 	// Hold the reference
-	m_texList.pushBack(m_alloc, tex);
+	m_microCmdb->pushObjectRef(tex);
 }
 
 void CommandBufferImpl::flushBarriers()
@@ -635,7 +639,7 @@ void CommandBufferImpl::copyBufferToTextureSurface(
 		BufferPtr shadow =
 			getGrManager().newInstance<Buffer>(shadowSize, BufferUsageBit::TRANSFER_ALL, BufferMapAccessBit::NONE);
 		const VkBuffer shadowHandle = shadow->m_impl->getHandle();
-		m_bufferList.pushBack(m_alloc, shadow);
+		m_microCmdb->pushObjectRef(shadow);
 
 		// Create the copy regions
 		DynamicArrayAuto<VkBufferCopy> copies(m_alloc);
@@ -687,8 +691,8 @@ void CommandBufferImpl::copyBufferToTextureSurface(
 		ANKI_ASSERT(0);
 	}
 
-	m_texList.pushBack(m_alloc, tex);
-	m_bufferList.pushBack(m_alloc, buff);
+	m_microCmdb->pushObjectRef(tex);
+	m_microCmdb->pushObjectRef(buff);
 }
 
 void CommandBufferImpl::copyBufferToTextureVolume(
@@ -739,7 +743,7 @@ void CommandBufferImpl::copyBufferToTextureVolume(
 		BufferPtr shadow =
 			getGrManager().newInstance<Buffer>(shadowSize, BufferUsageBit::TRANSFER_ALL, BufferMapAccessBit::NONE);
 		const VkBuffer shadowHandle = shadow->m_impl->getHandle();
-		m_bufferList.pushBack(m_alloc, shadow);
+		m_microCmdb->pushObjectRef(shadow);
 
 		// Create the copy regions
 		DynamicArrayAuto<VkBufferCopy> copies(m_alloc);
@@ -795,8 +799,8 @@ void CommandBufferImpl::copyBufferToTextureVolume(
 		ANKI_ASSERT(0);
 	}
 
-	m_texList.pushBack(m_alloc, tex);
-	m_bufferList.pushBack(m_alloc, buff);
+	m_microCmdb->pushObjectRef(tex);
+	m_microCmdb->pushObjectRef(buff);
 }
 
 } // end namespace anki

+ 18 - 19
src/anki/gr/vulkan/CommandBufferImpl.h

@@ -6,6 +6,7 @@
 #pragma once
 
 #include <anki/gr/vulkan/VulkanObject.h>
+#include <anki/gr/vulkan/CommandBufferFactory.h>
 #include <anki/gr/CommandBuffer.h>
 #include <anki/gr/Texture.h>
 #include <anki/gr/Buffer.h>
@@ -51,6 +52,11 @@ public:
 
 	ANKI_USE_RESULT Error init(const CommandBufferInitInfo& init);
 
+	void setFence(FencePtr& fence)
+	{
+		m_microCmdb->setFence(fence);
+	}
+
 	VkCommandBuffer getHandle() const
 	{
 		ANKI_ASSERT(m_handle);
@@ -78,7 +84,7 @@ public:
 		m_state.bindVertexBuffer(binding, stride, stepRate);
 		VkBuffer vkbuff = buff->m_impl->getHandle();
 		ANKI_CMD(vkCmdBindVertexBuffers(m_handle, binding, 1, &vkbuff, &offset), ANY_OTHER_COMMAND);
-		m_bufferList.pushBack(m_alloc, buff);
+		m_microCmdb->pushObjectRef(buff);
 	}
 
 	void setVertexAttribute(U32 location, U32 buffBinding, const PixelFormat& fmt, PtrSize relativeOffset)
@@ -92,7 +98,7 @@ public:
 		commandCommon();
 		ANKI_CMD(vkCmdBindIndexBuffer(m_handle, buff->m_impl->getHandle(), offset, convertIndexType(type)),
 			ANY_OTHER_COMMAND);
-		m_bufferList.pushBack(m_alloc, buff);
+		m_microCmdb->pushObjectRef(buff);
 	}
 
 	void setPrimitiveRestart(Bool enable)
@@ -199,7 +205,7 @@ public:
 		Texture& tex = *tex_;
 		const VkImageLayout lay = tex.m_impl->findLayoutFromTracker(m_texUsageTracker);
 		m_dsetState[set].bindTexture(realBinding, &tex, aspect, lay);
-		m_texList.pushBack(m_alloc, tex_);
+		m_microCmdb->pushObjectRef(tex_);
 	}
 
 	void bindTextureAndSampler(U32 set, U32 binding, TexturePtr& tex_, SamplerPtr sampler, DepthStencilAspectBit aspect)
@@ -209,8 +215,8 @@ public:
 		Texture& tex = *tex_;
 		const VkImageLayout lay = tex.m_impl->findLayoutFromTracker(m_texUsageTracker);
 		m_dsetState[set].bindTextureAndSampler(realBinding, &tex, sampler.get(), aspect, lay);
-		m_texList.pushBack(m_alloc, tex_);
-		m_samplerList.pushBack(m_alloc, sampler);
+		m_microCmdb->pushObjectRef(tex_);
+		m_microCmdb->pushObjectRef(sampler);
 	}
 
 	void bindImage(U32 set, U32 binding, TexturePtr& img, U32 level)
@@ -219,7 +225,7 @@ public:
 		const U realBinding =
 			binding + MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS + MAX_STORAGE_BUFFER_BINDINGS;
 		m_dsetState[set].bindImage(realBinding, img.get(), level);
-		m_texList.pushBack(m_alloc, img);
+		m_microCmdb->pushObjectRef(img);
 	}
 
 	void beginRenderPass(FramebufferPtr fb);
@@ -285,7 +291,7 @@ public:
 		commandCommon();
 		const U realBinding = MAX_TEXTURE_BINDINGS + binding;
 		m_dsetState[set].bindUniformBuffer(realBinding, buff.get(), offset, range);
-		m_bufferList.pushBack(m_alloc, buff);
+		m_microCmdb->pushObjectRef(buff);
 	}
 
 	void bindStorageBuffer(U32 set, U32 binding, BufferPtr& buff, PtrSize offset, PtrSize range)
@@ -293,7 +299,7 @@ public:
 		commandCommon();
 		const U realBinding = MAX_TEXTURE_BINDINGS + MAX_UNIFORM_BUFFER_BINDINGS + binding;
 		m_dsetState[set].bindStorageBuffer(realBinding, buff.get(), offset, range);
-		m_bufferList.pushBack(m_alloc, buff);
+		m_microCmdb->pushObjectRef(buff);
 	}
 
 	void copyBufferToTextureSurface(
@@ -322,6 +328,7 @@ public:
 private:
 	StackAllocator<U8> m_alloc;
 
+	MicroCommandBufferPtr m_microCmdb;
 	VkCommandBuffer m_handle = VK_NULL_HANDLE;
 	CommandBufferFlag m_flags = CommandBufferFlag::NONE;
 	Bool8 m_renderedToDefaultFb = false;
@@ -329,6 +336,9 @@ private:
 	Bool8 m_empty = true;
 	Bool m_beganRecording = false;
 	ThreadId m_tid = 0;
+#if ANKI_EXTRA_CHECKS
+	U32 m_commandCount = 0;
+#endif
 
 	U m_rpCommandCount = 0; ///< Number of drawcalls or pushed cmdbs in rp.
 	FramebufferPtr m_activeFb;
@@ -341,17 +351,6 @@ private:
 
 	ShaderProgramImpl* m_computeProg ANKI_DBG_NULLIFY;
 
-	/// @name cleanup_references
-	/// @{
-	List<FramebufferPtr> m_fbList;
-	List<TexturePtr> m_texList;
-	List<OcclusionQueryPtr> m_queryList;
-	List<BufferPtr> m_bufferList;
-	List<CommandBufferPtr> m_cmdbList;
-	List<ShaderProgramPtr> m_progs;
-	List<SamplerPtr> m_samplerList;
-	/// @}
-
 	VkSubpassContents m_subpassContents = VK_SUBPASS_CONTENTS_MAX_ENUM;
 
 	CommandBufferCommandType m_lastCmdType = CommandBufferCommandType::ANY_OTHER_COMMAND;

+ 18 - 13
src/anki/gr/vulkan/CommandBufferImpl.inl.h

@@ -145,7 +145,7 @@ inline void CommandBufferImpl::setTextureBarrierRange(
 
 	setImageBarrier(srcStage, srcAccess, oldLayout, dstStage, dstAccess, newLayout, impl.m_imageHandle, range);
 
-	m_texList.pushBack(m_alloc, tex);
+	m_microCmdb->pushObjectRef(tex);
 }
 
 inline void CommandBufferImpl::setTextureSurfaceBarrier(
@@ -238,7 +238,7 @@ inline void CommandBufferImpl::setBufferBarrier(
 
 	setBufferBarrier(srcStage, srcAccess, dstStage, dstAccess, offset, size, impl.getHandle());
 
-	m_bufferList.pushBack(m_alloc, buff);
+	m_microCmdb->pushObjectRef(buff);
 }
 
 inline void CommandBufferImpl::drawArrays(
@@ -350,7 +350,7 @@ inline void CommandBufferImpl::resetOcclusionQuery(OcclusionQueryPtr query)
 	ANKI_CMD(vkCmdResetQueryPool(m_handle, handle, idx, 1), ANY_OTHER_COMMAND);
 #endif
 
-	m_queryList.pushBack(m_alloc, query);
+	m_microCmdb->pushObjectRef(query);
 }
 
 inline void CommandBufferImpl::beginOcclusionQuery(OcclusionQueryPtr query)
@@ -363,7 +363,7 @@ inline void CommandBufferImpl::beginOcclusionQuery(OcclusionQueryPtr query)
 
 	ANKI_CMD(vkCmdBeginQuery(m_handle, handle, idx, 0), ANY_OTHER_COMMAND);
 
-	m_queryList.pushBack(m_alloc, query);
+	m_microCmdb->pushObjectRef(query);
 }
 
 inline void CommandBufferImpl::endOcclusionQuery(OcclusionQueryPtr query)
@@ -376,7 +376,7 @@ inline void CommandBufferImpl::endOcclusionQuery(OcclusionQueryPtr query)
 
 	ANKI_CMD(vkCmdEndQuery(m_handle, handle, idx), ANY_OTHER_COMMAND);
 
-	m_queryList.pushBack(m_alloc, query);
+	m_microCmdb->pushObjectRef(query);
 }
 
 inline void CommandBufferImpl::clearTextureInternal(
@@ -400,7 +400,7 @@ inline void CommandBufferImpl::clearTextureInternal(
 		ANKI_ASSERT(0 && "TODO");
 	}
 
-	m_texList.pushBack(m_alloc, tex);
+	m_microCmdb->pushObjectRef(tex);
 }
 
 inline void CommandBufferImpl::clearTextureSurface(
@@ -455,7 +455,7 @@ inline void CommandBufferImpl::pushSecondLevelCommandBuffer(CommandBufferPtr cmd
 #endif
 
 	++m_rpCommandCount;
-	m_cmdbList.pushBack(m_alloc, cmdb);
+	m_microCmdb->pushObjectRef(cmdb);
 }
 
 inline void CommandBufferImpl::drawcallCommon()
@@ -553,6 +553,11 @@ inline void CommandBufferImpl::commandCommon()
 
 	ANKI_ASSERT(!m_finalized);
 	ANKI_ASSERT(m_handle);
+	
+#if ANKI_EXTRA_CHECKS
+	++m_commandCount;
+#endif
+	
 	m_empty = false;
 
 	if(ANKI_UNLIKELY(!m_beganRecording))
@@ -608,7 +613,7 @@ inline void CommandBufferImpl::fillBuffer(BufferPtr buff, PtrSize offset, PtrSiz
 
 	ANKI_CMD(vkCmdFillBuffer(m_handle, impl.getHandle(), offset, size, value), ANY_OTHER_COMMAND);
 
-	m_bufferList.pushBack(m_alloc, buff);
+	m_microCmdb->pushObjectRef(buff);
 }
 
 inline void CommandBufferImpl::writeOcclusionQueryResultToBuffer(
@@ -651,8 +656,8 @@ inline void CommandBufferImpl::writeOcclusionQueryResultToBuffer(
 		ANY_OTHER_COMMAND);
 #endif
 
-	m_queryList.pushBack(m_alloc, query);
-	m_bufferList.pushBack(m_alloc, buff);
+	m_microCmdb->pushObjectRef(query);
+	m_microCmdb->pushObjectRef(buff);
 }
 
 inline void CommandBufferImpl::bindShaderProgram(ShaderProgramPtr& prog)
@@ -683,7 +688,7 @@ inline void CommandBufferImpl::bindShaderProgram(ShaderProgramPtr& prog)
 		}
 	}
 
-	m_progs.pushBack(m_alloc, prog);
+	m_microCmdb->pushObjectRef(prog);
 }
 
 inline void CommandBufferImpl::copyBufferToBuffer(
@@ -699,8 +704,8 @@ inline void CommandBufferImpl::copyBufferToBuffer(
 	ANKI_CMD(
 		vkCmdCopyBuffer(m_handle, src->m_impl->getHandle(), dst->m_impl->getHandle(), 1, &region), ANY_OTHER_COMMAND);
 
-	m_bufferList.pushBack(m_alloc, src);
-	m_bufferList.pushBack(m_alloc, dst);
+	m_microCmdb->pushObjectRef(src);
+	m_microCmdb->pushObjectRef(dst);
 }
 
 } // end namespace anki

+ 5 - 54
src/anki/gr/vulkan/GrManagerImpl.cpp

@@ -26,6 +26,8 @@ GrManagerImpl::~GrManagerImpl()
 		m_queue = VK_NULL_HANDLE;
 	}
 
+	m_cmdbFactory.destroy();
+
 	// SECOND THING: The destroy everything that has a reference to GrObjects.
 	for(auto& x : m_backbuffers)
 	{
@@ -41,12 +43,8 @@ GrManagerImpl::~GrManagerImpl()
 		x.m_presentFence.reset(nullptr);
 		x.m_acquireSemaphore.reset(nullptr);
 		x.m_renderSemaphore.reset(nullptr);
-
-		x.m_cmdbsSubmitted.destroy(getAllocator());
 	}
 
-	m_perThread.destroy(getAllocator());
-
 	if(m_samplerCache)
 	{
 		getAllocator().deleteInstance(m_samplerCache);
@@ -119,6 +117,8 @@ Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
 	ANKI_CHECK(m_pplineCache.init(m_device, m_physicalDevice, init.m_cacheDirectory, *init.m_config, getAllocator()));
 
 	ANKI_CHECK(initMemory(*init.m_config));
+	
+	ANKI_CHECK(m_cmdbFactory.init(getAllocator(), m_device, m_queueIdx));
 
 	for(PerFrame& f : m_perFrame)
 	{
@@ -780,55 +780,6 @@ void GrManagerImpl::resetFrame(PerFrame& frame)
 	frame.m_presentFence.reset(nullptr);
 	frame.m_acquireSemaphore.reset(nullptr);
 	frame.m_renderSemaphore.reset(nullptr);
-
-	frame.m_cmdbsSubmitted.destroy(getAllocator());
-}
-
-GrManagerImpl::PerThread& GrManagerImpl::getPerThreadCache(ThreadId tid)
-{
-	PerThread* thread = nullptr;
-	LockGuard<SpinLock> lock(m_perThreadMtx);
-
-	// Find or create a record
-	auto it = m_perThread.find(tid);
-	if(it != m_perThread.getEnd())
-	{
-		thread = &(*it);
-	}
-	else
-	{
-		m_perThread.emplaceBack(getAllocator(), tid);
-		it = m_perThread.find(tid);
-		thread = &(*it);
-	}
-
-	return *thread;
-}
-
-VkCommandBuffer GrManagerImpl::newCommandBuffer(ThreadId tid, CommandBufferFlag cmdbFlags)
-{
-	// Get the per thread cache
-	PerThread& thread = getPerThreadCache(tid);
-
-	// Try initialize the recycler
-	if(ANKI_UNLIKELY(!thread.m_cmdbs.isCreated()))
-	{
-		Error err = thread.m_cmdbs.init(getAllocator(), m_device, m_queueIdx);
-		if(err)
-		{
-			ANKI_VK_LOGF("Cannot recover");
-		}
-	}
-
-	return thread.m_cmdbs.newCommandBuffer(cmdbFlags);
-}
-
-void GrManagerImpl::deleteCommandBuffer(VkCommandBuffer cmdb, CommandBufferFlag cmdbFlags, ThreadId tid)
-{
-	// Get the per thread cache
-	PerThread& thread = getPerThreadCache(tid);
-
-	thread.m_cmdbs.deleteCommandBuffer(cmdb, cmdbFlags);
 }
 
 void GrManagerImpl::flushCommandBuffer(CommandBufferPtr cmdb, Bool wait)
@@ -867,7 +818,7 @@ void GrManagerImpl::flushCommandBuffer(CommandBufferPtr cmdb, Bool wait)
 	submit.commandBufferCount = 1;
 	submit.pCommandBuffers = &handle;
 
-	frame.m_cmdbsSubmitted.pushBack(getAllocator(), cmdb);
+	impl.setFence(fence);
 
 	ANKI_TRACE_START_EVENT(VK_QUEUE_SUBMIT);
 	ANKI_VK_CHECKF(vkQueueSubmit(m_queue, 1, &submit, fence->getHandle()));

+ 6 - 39
src/anki/gr/vulkan/GrManagerImpl.h

@@ -11,7 +11,7 @@
 #include <anki/gr/vulkan/Fence.h>
 #include <anki/gr/vulkan/QueryExtra.h>
 #include <anki/gr/vulkan/DescriptorSet.h>
-#include <anki/gr/vulkan/CommandBufferExtra.h>
+#include <anki/gr/vulkan/CommandBufferFactory.h>
 #include <anki/gr/vulkan/PipelineLayout.h>
 #include <anki/gr/vulkan/PipelineCache.h>
 #include <anki/util/HashMap.h>
@@ -73,9 +73,10 @@ public:
 	/// @name object_creation
 	/// @{
 
-	VkCommandBuffer newCommandBuffer(ThreadId tid, CommandBufferFlag cmdbFlags);
-
-	void deleteCommandBuffer(VkCommandBuffer cmdb, CommandBufferFlag cmdbFlags, ThreadId tid);
+	CommandBufferFactory& getCommandBufferFactory()
+	{
+		return m_cmdbFactory;
+	}
 
 	FencePtr newFence()
 	{
@@ -239,9 +240,6 @@ private:
 
 		/// The semaphore that the submit that renders to the default FB.
 		GrSemaphorePtr m_renderSemaphore;
-
-		/// Keep it here for deferred cleanup.
-		List<CommandBufferPtr> m_cmdbsSubmitted;
 	};
 
 	VkSurfaceKHR m_surface = VK_NULL_HANDLE;
@@ -261,36 +259,7 @@ private:
 	GpuMemoryManager m_gpuMemManager;
 	/// @}
 
-	/// @name Per_thread_cache
-	/// @{
-
-	class PerThreadHasher
-	{
-	public:
-		U64 operator()(const ThreadId& b) const
-		{
-			return b;
-		}
-	};
-
-	class PerThreadCompare
-	{
-	public:
-		Bool operator()(const ThreadId& a, const ThreadId& b) const
-		{
-			return a == b;
-		}
-	};
-
-	/// Per thread cache.
-	class PerThread
-	{
-	public:
-		CommandBufferFactory m_cmdbs;
-	};
-
-	HashMap<ThreadId, PerThread, PerThreadHasher, PerThreadCompare> m_perThread;
-	SpinLock m_perThreadMtx;
+	CommandBufferFactory m_cmdbFactory;
 
 	FenceFactory m_fences;
 	GrSemaphoreFactory m_semaphores;
@@ -329,8 +298,6 @@ private:
 #endif
 
 	void resetFrame(PerFrame& frame);
-
-	PerThread& getPerThreadCache(ThreadId tid);
 };
 /// @}
 

+ 3 - 0
src/anki/util/List.inl.h

@@ -52,6 +52,7 @@ template<typename T, typename TNode>
 void ListBase<T, TNode>::pushBackNode(TNode* node)
 {
 	ANKI_ASSERT(node);
+	ANKI_ASSERT(node->m_next == nullptr && node->m_prev == nullptr);
 
 	if(m_tail != nullptr)
 	{
@@ -71,6 +72,7 @@ template<typename T, typename TNode>
 void ListBase<T, TNode>::pushFrontNode(TNode* node)
 {
 	ANKI_ASSERT(node);
+	ANKI_ASSERT(node->m_next == nullptr && node->m_prev == nullptr);
 
 	if(m_head != nullptr)
 	{
@@ -90,6 +92,7 @@ template<typename T, typename TNode>
 void ListBase<T, TNode>::insertNode(TNode* pos, TNode* node)
 {
 	ANKI_ASSERT(node);
+	ANKI_ASSERT(node->m_next == nullptr && node->m_prev == nullptr);
 
 	if(pos == nullptr)
 	{

+ 1 - 1
tests/gr/Gr.cpp

@@ -419,7 +419,7 @@ ANKI_TEST(Gr, ClearScreen)
 		gr->beginFrame();
 
 		CommandBufferInitInfo cinit;
-		cinit.m_flags = CommandBufferFlag::GRAPHICS_WORK;
+		cinit.m_flags = CommandBufferFlag::GRAPHICS_WORK | CommandBufferFlag::SMALL_BATCH;
 		CommandBufferPtr cmdb = gr->newInstance<CommandBuffer>(cinit);
 
 		cmdb->beginRenderPass(fb);