Browse Source

Refactor fences a bit

Panagiotis Christopoulos Charitos 5 months ago
parent
commit
a6c2457201
45 changed files with 1016 additions and 996 deletions
  1. 3 7
      AnKi/Core/App.cpp
  2. 3 1
      AnKi/Core/StatsSet.h
  3. 107 0
      AnKi/Gr/BackendCommon/FrameGarbageCollector.h
  4. 148 0
      AnKi/Gr/BackendCommon/MicroFenceFactory.h
  5. 166 0
      AnKi/Gr/BackendCommon/MicroFenceFactory.inl.h
  6. 6 1
      AnKi/Gr/BackendCommon/MicroObjectRecycler.inl.h
  7. 1 1
      AnKi/Gr/D3D/D3DBuffer.cpp
  8. 1 1
      AnKi/Gr/D3D/D3DCommandBuffer.cpp
  9. 1 1
      AnKi/Gr/D3D/D3DCommandBuffer.h
  10. 1 1
      AnKi/Gr/D3D/D3DCommandBufferFactory.cpp
  11. 2 2
      AnKi/Gr/D3D/D3DCommandBufferFactory.h
  12. 2 147
      AnKi/Gr/D3D/D3DFence.cpp
  13. 81 49
      AnKi/Gr/D3D/D3DFence.h
  14. 6 91
      AnKi/Gr/D3D/D3DFrameGarbageCollector.cpp
  15. 12 38
      AnKi/Gr/D3D/D3DFrameGarbageCollector.h
  16. 104 60
      AnKi/Gr/D3D/D3DGrManager.cpp
  17. 10 1
      AnKi/Gr/D3D/D3DGrManager.h
  18. 3 3
      AnKi/Gr/D3D/D3DQueryFactory.cpp
  19. 1 1
      AnKi/Gr/D3D/D3DQueryFactory.h
  20. 2 2
      AnKi/Gr/D3D/D3DSwapchainFactory.h
  21. 1 1
      AnKi/Gr/D3D/D3DTexture.cpp
  22. 5 2
      AnKi/Gr/GrManager.h
  23. 1 1
      AnKi/Gr/Vulkan/VkAccelerationStructure.cpp
  24. 1 1
      AnKi/Gr/Vulkan/VkBuffer.cpp
  25. 1 1
      AnKi/Gr/Vulkan/VkCommandBuffer.h
  26. 1 1
      AnKi/Gr/Vulkan/VkCommandBufferFactory.cpp
  27. 2 2
      AnKi/Gr/Vulkan/VkCommandBufferFactory.h
  28. 2 2
      AnKi/Gr/Vulkan/VkDescriptor.cpp
  29. 4 104
      AnKi/Gr/Vulkan/VkFenceFactory.cpp
  30. 47 77
      AnKi/Gr/Vulkan/VkFenceFactory.h
  31. 25 139
      AnKi/Gr/Vulkan/VkFrameGarbageCollector.cpp
  32. 10 53
      AnKi/Gr/Vulkan/VkFrameGarbageCollector.h
  33. 200 176
      AnKi/Gr/Vulkan/VkGrManager.cpp
  34. 6 12
      AnKi/Gr/Vulkan/VkGrManager.h
  35. 4 1
      AnKi/Gr/Vulkan/VkSemaphoreFactory.cpp
  36. 3 3
      AnKi/Gr/Vulkan/VkSemaphoreFactory.h
  37. 2 2
      AnKi/Gr/Vulkan/VkSwapchainFactory.h
  38. 2 1
      AnKi/Gr/Vulkan/VkTexture.cpp
  39. 12 1
      AnKi/Renderer/FinalComposite.cpp
  40. 7 2
      AnKi/Renderer/Renderer.cpp
  41. 1 1
      AnKi/Renderer/Renderer.h
  42. 9 3
      Tests/Gr/Gr.cpp
  43. 4 1
      Tests/Gr/GrAsyncCompute.cpp
  44. 3 1
      Tests/Gr/GrMeshShaders.cpp
  45. 3 1
      Tests/Ui/Ui.cpp

+ 3 - 7
AnKi/Core/App.cpp

@@ -397,17 +397,13 @@ Error App::mainLoop()
 			prevUpdateTime = crntTime;
 			prevUpdateTime = crntTime;
 			crntTime = (!benchmarkMode) ? HighRezTimer::getCurrentTime() : (prevUpdateTime + 1.0_sec / 60.0_sec);
 			crntTime = (!benchmarkMode) ? HighRezTimer::getCurrentTime() : (prevUpdateTime + 1.0_sec / 60.0_sec);
 
 
-			// Update
 			ANKI_CHECK(Input::getSingleton().handleEvents());
 			ANKI_CHECK(Input::getSingleton().handleEvents());
+			GrManager::getSingleton().beginFrame();
 
 
-			// User update
 			ANKI_CHECK(userMainLoop(quit, crntTime - prevUpdateTime));
 			ANKI_CHECK(userMainLoop(quit, crntTime - prevUpdateTime));
-
 			SceneGraph::getSingleton().update(prevUpdateTime, crntTime);
 			SceneGraph::getSingleton().update(prevUpdateTime, crntTime);
 
 
-			// Render
-			TexturePtr presentableTex = GrManager::getSingleton().acquireNextPresentableTexture();
-			ANKI_CHECK(Renderer::getSingleton().render(presentableTex.get()));
+			ANKI_CHECK(Renderer::getSingleton().render());
 
 
 			// If we get stats exclude the time of GR because it forces some GPU-CPU serialization. We don't want to count that
 			// If we get stats exclude the time of GR because it forces some GPU-CPU serialization. We don't want to count that
 			Second grTime = 0.0;
 			Second grTime = 0.0;
@@ -416,7 +412,7 @@ Error App::mainLoop()
 				grTime = HighRezTimer::getCurrentTime();
 				grTime = HighRezTimer::getCurrentTime();
 			}
 			}
 
 
-			GrManager::getSingleton().swapBuffers();
+			GrManager::getSingleton().endFrame();
 
 
 			if(benchmarkMode || g_displayStatsCVar > 0) [[unlikely]]
 			if(benchmarkMode || g_displayStatsCVar > 0) [[unlikely]]
 			{
 			{

+ 3 - 1
AnKi/Core/StatsSet.h

@@ -39,12 +39,14 @@ enum class StatCategory : U8
 	kGpuMem,
 	kGpuMem,
 	kGpuMisc,
 	kGpuMisc,
 	kRenderer,
 	kRenderer,
+	kGr,
 	kMisc,
 	kMisc,
 
 
 	kCount,
 	kCount,
 };
 };
 
 
-inline constexpr Array<CString, U32(StatCategory::kCount)> kStatCategoryTexts = {"Time", "CPU memory", "GPU memory", "GPU misc", "Renderer", "Misc"};
+inline constexpr Array<CString, U32(StatCategory::kCount)> kStatCategoryTexts = {"Time",     "CPU memory", "GPU memory", "GPU misc",
+																				 "Renderer", "GFX API",    "Misc"};
 
 
 /// A stats counter.
 /// A stats counter.
 class StatCounter
 class StatCounter

+ 107 - 0
AnKi/Gr/BackendCommon/FrameGarbageCollector.h

@@ -0,0 +1,107 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Gr/BackendCommon/Common.h>
+#include <AnKi/Util/Thread.h>
+
+namespace anki {
+
+/// @addtogroup graphics
+/// @{
+
+/// This class gathers various garbages and disposes them when in some later frame where it is safe to do so. This is used on bindless textures and
+/// buffers where we have to wait until the frame where they were deleted is done.
+template<typename TTextureGarbage, typename TBufferGarbage, typename TASGarbage>
+class FrameGarbageCollector
+{
+public:
+	FrameGarbageCollector() = default;
+
+	~FrameGarbageCollector()
+	{
+		destroy();
+	}
+
+	void destroy()
+	{
+		for(FrameGarbage& frame : m_frames)
+		{
+			collectGarbage(frame);
+		}
+	}
+
+	/// Sets a new frame and collects garbage as well.
+	/// @note It's thread-safe.
+	void beginFrameAndCollectGarbage()
+	{
+		LockGuard lock(m_mtx);
+		m_frame = (m_frame + 1) % m_frames.getSize();
+		FrameGarbage& frame = m_frames[m_frame];
+		collectGarbage(frame);
+	}
+
+	/// @note It's thread-safe.
+	void newTextureGarbage(TTextureGarbage* textureGarbage)
+	{
+		ANKI_ASSERT(textureGarbage);
+		LockGuard lock(m_mtx);
+		FrameGarbage& frame = m_frames[m_frame];
+		frame.m_textureGarbage.pushBack(textureGarbage);
+	}
+
+	/// @note It's thread-safe.
+	void newBufferGarbage(TBufferGarbage* bufferGarbage)
+	{
+		ANKI_ASSERT(bufferGarbage);
+		LockGuard lock(m_mtx);
+		FrameGarbage& frame = m_frames[m_frame];
+		frame.m_bufferGarbage.pushBack(bufferGarbage);
+	}
+
+	/// @note It's thread-safe.
+	void newASGarbage(TASGarbage* garbage)
+	{
+		ANKI_ASSERT(garbage);
+		LockGuard lock(m_mtx);
+		FrameGarbage& frame = m_frames[m_frame];
+		frame.m_asGarbage.pushBack(garbage);
+	}
+
+private:
+	class FrameGarbage
+	{
+	public:
+		IntrusiveList<TTextureGarbage> m_textureGarbage;
+		IntrusiveList<TBufferGarbage> m_bufferGarbage;
+		IntrusiveList<TASGarbage> m_asGarbage;
+	};
+
+	Mutex m_mtx;
+	Array<FrameGarbage, kMaxFramesInFlight> m_frames;
+	U32 m_frame = 0;
+
+	static void collectGarbage(FrameGarbage& frame)
+	{
+		while(!frame.m_textureGarbage.isEmpty())
+		{
+			deleteInstance(GrMemoryPool::getSingleton(), frame.m_textureGarbage.popBack());
+		}
+
+		while(!frame.m_bufferGarbage.isEmpty())
+		{
+			deleteInstance(GrMemoryPool::getSingleton(), frame.m_bufferGarbage.popBack());
+		}
+
+		while(!frame.m_asGarbage.isEmpty())
+		{
+			deleteInstance(GrMemoryPool::getSingleton(), frame.m_asGarbage.popBack());
+		}
+	}
+};
+/// @}
+
+} // end namespace anki

+ 148 - 0
AnKi/Gr/BackendCommon/MicroFenceFactory.h

@@ -0,0 +1,148 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#pragma once
+
+#include <AnKi/Gr/BackendCommon/Common.h>
+#include <AnKi/Util/BlockArray.h>
+
+namespace anki {
+
+/// @addtogroup graphics
+/// @{
+
+template<typename TImplementation>
+class MicroFence
+{
+public:
+	template<typename>
+	friend class MicroFenceFactory;
+
+public:
+	MicroFence() = default;
+
+	MicroFence(const MicroFence&) = delete; // Non-copyable
+
+	MicroFence& operator=(const MicroFence&) = delete; // Non-copyable
+
+	const TImplementation& getImplementation() const
+	{
+		LockGuard lock(m_lock);
+		ANKI_ASSERT(m_state == kUnsignaled);
+		return m_impl;
+	}
+
+	TImplementation& getImplementation()
+	{
+		LockGuard lock(m_lock);
+		ANKI_ASSERT(m_state == kUnsignaled);
+		return m_impl;
+	}
+
+	void retain() const
+	{
+		m_refcount.fetchAdd(1);
+	}
+
+	I32 release() const
+	{
+		return m_refcount.fetchSub(1);
+	}
+
+	Bool clientWait(Second seconds)
+	{
+		LockGuard lock(m_lock);
+
+		Bool bSignaled;
+		if(m_state == kSignaled)
+		{
+			bSignaled = true;
+		}
+		else if(seconds == 0.0)
+		{
+			bSignaled = m_impl.signaled();
+		}
+		else
+		{
+			// Not signaled and has handle
+			seconds = min<Second>(seconds, g_gpuTimeoutCVar);
+			bSignaled = m_impl.clientWait(seconds);
+		}
+
+		if(bSignaled)
+		{
+			m_state = kSignaled;
+		}
+
+		return bSignaled;
+	}
+
+	Bool signaled()
+	{
+		LockGuard lock(m_lock);
+
+		Bool bSignaled;
+		if(m_state == kSignaled)
+		{
+			bSignaled = true;
+		}
+		else
+		{
+			bSignaled = m_impl.signaled();
+			if(bSignaled)
+			{
+				m_state = kSignaled;
+			}
+		}
+
+		return bSignaled;
+	}
+
+private:
+	enum State : U8
+	{
+		kUnsignaled,
+		kSignaled
+	};
+
+	TImplementation m_impl;
+	mutable Atomic<I32> m_refcount = {0};
+	mutable SpinLock m_lock;
+	U16 m_arrayIdx = kMaxU16;
+	State m_state = kUnsignaled;
+	GrString m_name;
+};
+
+/// A factory of fences. It's designed to minimize the number of alive GFX API fences. If creating many VkFences on Linux the application may run out
+/// of file descriptors.
+template<typename TImplementation>
+class MicroFenceFactory
+{
+public:
+	using MicroFence = MicroFence<TImplementation>;
+
+	/// Limit the alive fences to avoid having too many file descriptors used in Linux.
+	static constexpr U32 kMaxAliveFences = 32;
+
+	MicroFenceFactory() = default;
+
+	~MicroFenceFactory();
+
+	/// Create a new fence pointer.
+	MicroFence* newFence(CString name = "unnamed");
+
+	void releaseFence(MicroFence* fence);
+
+private:
+	GrBlockArray<MicroFence> m_fences;
+	U32 m_aliveFenceCount = 0;
+	Mutex m_mtx;
+	BitSet<1024, U64> m_markedForDeletionMask = {false}; // Always last for better caching
+};
+/// @}
+
+} // end namespace anki
+
+#include <AnKi/Gr/BackendCommon/MicroFenceFactory.inl.h>

+ 166 - 0
AnKi/Gr/BackendCommon/MicroFenceFactory.inl.h

@@ -0,0 +1,166 @@
+// Copyright (C) 2009-present, Panagiotis Christopoulos Charitos and contributors.
+// All rights reserved.
+// Code licensed under the BSD License.
+// http://www.anki3d.org/LICENSE
+
+#include <AnKi/Gr/BackendCommon/MicroFenceFactory.h>
+#include <AnKi/Core/StatsSet.h>
+
+namespace anki {
+
+inline StatCounter g_fenceCountStatVar(StatCategory::kGr, "Fence count", StatFlag::kNone);
+
+template<typename TImplementation>
+MicroFenceFactory<TImplementation>::~MicroFenceFactory()
+{
+	GrDynamicArray<U32> fenceIdxToDelete;
+	for(U32 idx = m_markedForDeletionMask.getLeastSignificantBit(); idx < kMaxU32; idx = m_markedForDeletionMask.getLeastSignificantBit())
+	{
+		fenceIdxToDelete.emplaceBack(idx);
+		m_markedForDeletionMask.unset(idx);
+	}
+
+	for(U32 idx : fenceIdxToDelete)
+	{
+		MicroFence& fence = m_fences[idx];
+
+		if(fence.m_impl)
+		{
+			fence.m_impl.destroy();
+			ANKI_ASSERT(m_aliveFenceCount > 0);
+			--m_aliveFenceCount;
+			g_fenceCountStatVar.decrement(1_U64);
+		}
+
+		m_fences.erase(idx);
+	}
+
+	ANKI_ASSERT(!m_markedForDeletionMask.getAnySet());
+	ANKI_ASSERT(m_fences.getSize() == 0);
+	ANKI_ASSERT(m_aliveFenceCount == 0);
+}
+
+template<typename TImplementation>
+MicroFenceFactory<TImplementation>::MicroFence* MicroFenceFactory<TImplementation>::newFence(CString name)
+{
+	MicroFence* out = nullptr;
+
+	LockGuard<Mutex> lock(m_mtx);
+
+	// Trim fences if needed
+	if(m_aliveFenceCount > kMaxAliveFences * 80 / 100)
+	{
+		GrDynamicArray<U32> toDelete;
+		for(auto it = m_fences.getBegin(); it != m_fences.getEnd(); ++it)
+		{
+			LockGuard lock(it->m_lock);
+
+			const Bool markedForDeletion = m_markedForDeletionMask.get(it.getArrayIndex());
+
+			if(markedForDeletion)
+			{
+				toDelete.emplaceBack(it.getArrayIndex());
+			}
+
+			if(!it->m_impl)
+			{
+				continue;
+			}
+
+			// Marked for deletion or signaled will loose their fence
+
+			Bool destroyFence = it->m_state == MicroFence::kSignaled;
+			if(!destroyFence)
+			{
+				const Bool signaled = it->m_impl.signaled();
+				destroyFence = signaled;
+
+				if(signaled)
+				{
+					it->m_state = MicroFence::kSignaled;
+				}
+			}
+
+			if(destroyFence)
+			{
+				if(it->m_impl)
+				{
+					it->m_impl.destroy();
+					ANKI_ASSERT(m_aliveFenceCount > 0);
+					--m_aliveFenceCount;
+					g_fenceCountStatVar.decrement(1_U64);
+				}
+			}
+		}
+
+		for(U32 i = 0; i < toDelete.getSize(); ++i)
+		{
+			m_fences.erase(toDelete[i]);
+			ANKI_ASSERT(m_markedForDeletionMask.get(toDelete[i]));
+			m_markedForDeletionMask.unset(toDelete[i]);
+		}
+	}
+
+	// Find to recycle
+	const U32 idx = m_markedForDeletionMask.getLeastSignificantBit();
+	if(idx != kMaxU32)
+	{
+		m_markedForDeletionMask.unset(idx);
+		out = &m_fences[idx];
+	}
+
+	if(!out)
+	{
+		// Create new
+		auto it = m_fences.emplace();
+		out = &(*it);
+		out->m_arrayIdx = U16(it.getArrayIndex());
+	}
+
+	if(!out->m_impl)
+	{
+		// Create new
+
+		out->m_impl.create();
+
+		g_fenceCountStatVar.increment(1_U64);
+		++m_aliveFenceCount;
+		if(m_aliveFenceCount > kMaxAliveFences)
+		{
+			ANKI_GR_LOGW("Too many alive fences (%u). You may run out of file descriptors", m_aliveFenceCount);
+		}
+	}
+	else
+	{
+		// Recycle
+		if(!out->signaled())
+		{
+			ANKI_GR_LOGW("Fence marked for deletion but it's not signaled: %s", out->m_name.cstr());
+		}
+
+		out->m_impl.reset();
+	}
+
+	out->m_state = MicroFence::kUnsignaled;
+	out->m_impl.setName(name);
+	out->m_name = name;
+
+	ANKI_ASSERT(out->m_refcount.getNonAtomically() == 0);
+	return out;
+}
+
+template<typename TImplementation>
+void MicroFenceFactory<TImplementation>::releaseFence(MicroFence* fence)
+{
+	LockGuard lock(m_mtx);
+
+	ANKI_ASSERT(!m_markedForDeletionMask.get(fence->m_arrayIdx));
+	m_markedForDeletionMask.set(fence->m_arrayIdx);
+
+	if(!fence->signaled())
+	{
+		ANKI_GR_LOGW("Fence marked for deletion but it's not signaled: %s", fence->m_name.cstr());
+	}
+}
+
+} // end namespace anki

+ 6 - 1
AnKi/Gr/BackendCommon/MicroObjectRecycler.inl.h

@@ -76,6 +76,11 @@ void MicroObjectRecycler<T>::recycle(T* mobj)
 
 
 	LockGuard<Mutex> lock(m_mtx);
 	LockGuard<Mutex> lock(m_mtx);
 
 
+	if(!mobj->getFence())
+	{
+		ANKI_GR_LOGW("Object is recycled and doesn't have a fence");
+	}
+
 	Object obj;
 	Object obj;
 	obj.m_fenceDone = !mobj->getFence();
 	obj.m_fenceDone = !mobj->getFence();
 	obj.m_microObject = mobj;
 	obj.m_microObject = mobj;
@@ -102,7 +107,7 @@ void MicroObjectRecycler<T>::checkDoneFences()
 			ANKI_ASSERT(!mobj.getFence());
 			ANKI_ASSERT(!mobj.getFence());
 		}
 		}
 
 
-		if(!obj.m_fenceDone && mobj.getFence() && mobj.getFence()->done())
+		if(!obj.m_fenceDone && mobj.getFence() && mobj.getFence()->signaled())
 		{
 		{
 			mobj.setFence(nullptr);
 			mobj.setFence(nullptr);
 			mobj.onFenceDone();
 			mobj.onFenceDone();

+ 1 - 1
AnKi/Gr/D3D/D3DBuffer.cpp

@@ -77,7 +77,7 @@ BufferImpl::~BufferImpl()
 
 
 	BufferGarbage* garbage = anki::newInstance<BufferGarbage>(GrMemoryPool::getSingleton());
 	BufferGarbage* garbage = anki::newInstance<BufferGarbage>(GrMemoryPool::getSingleton());
 	garbage->m_resource = m_resource;
 	garbage->m_resource = m_resource;
-	FrameGarbageCollector::getSingleton().newBufferGarbage(garbage);
+	D3DFrameGarbageCollector::getSingleton().newBufferGarbage(garbage);
 }
 }
 
 
 Error BufferImpl::init(const BufferInitInfo& inf)
 Error BufferImpl::init(const BufferInitInfo& inf)

+ 1 - 1
AnKi/Gr/D3D/D3DCommandBuffer.cpp

@@ -938,7 +938,7 @@ Error CommandBufferImpl::init(const CommandBufferInitInfo& init)
 	return Error::kNone;
 	return Error::kNone;
 }
 }
 
 
-void CommandBufferImpl::postSubmitWork(MicroFence* fence)
+void CommandBufferImpl::postSubmitWork(D3DMicroFence* fence)
 {
 {
 	for(QueryHandle handle : m_timestampQueries)
 	for(QueryHandle handle : m_timestampQueries)
 	{
 	{

+ 1 - 1
AnKi/Gr/D3D/D3DCommandBuffer.h

@@ -37,7 +37,7 @@ public:
 		return *m_mcmdb;
 		return *m_mcmdb;
 	}
 	}
 
 
-	void postSubmitWork(MicroFence* fence);
+	void postSubmitWork(D3DMicroFence* fence);
 
 
 private:
 private:
 	D3D12GraphicsCommandListX* m_cmdList = nullptr; // Cache it.
 	D3D12GraphicsCommandListX* m_cmdList = nullptr; // Cache it.

+ 1 - 1
AnKi/Gr/D3D/D3DCommandBufferFactory.cpp

@@ -9,7 +9,7 @@
 
 
 namespace anki {
 namespace anki {
 
 
-static StatCounter g_commandBufferCountStatVar(StatCategory::kMisc, "CommandBufferCount", StatFlag::kNone);
+static StatCounter g_commandBufferCountStatVar(StatCategory::kGr, "CommandBufferCount", StatFlag::kNone);
 
 
 MicroCommandBuffer::~MicroCommandBuffer()
 MicroCommandBuffer::~MicroCommandBuffer()
 {
 {

+ 2 - 2
AnKi/Gr/D3D/D3DCommandBufferFactory.h

@@ -57,13 +57,13 @@ public:
 		reset();
 		reset();
 	}
 	}
 
 
-	void setFence(MicroFence* fence)
+	void setFence(D3DMicroFence* fence)
 	{
 	{
 		m_fence.reset(fence);
 		m_fence.reset(fence);
 	}
 	}
 
 
 	/// Interface method.
 	/// Interface method.
-	MicroFence* getFence() const
+	D3DMicroFence* getFence() const
 	{
 	{
 		return m_fence.tryGet();
 		return m_fence.tryGet();
 	}
 	}

+ 2 - 147
AnKi/Gr/D3D/D3DFence.cpp

@@ -8,161 +8,16 @@
 
 
 namespace anki {
 namespace anki {
 
 
-MicroFence::MicroFence()
-{
-	ANKI_D3D_CHECKF(getDevice().CreateFence(m_value.getNonAtomically(), D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)));
-
-	m_event = CreateEvent(nullptr, false, false, nullptr);
-	if(!m_event)
-	{
-		ANKI_D3D_LOGF("CreateEvent() failed");
-	}
-}
-
-void MicroFence::gpuSignal(GpuQueueType queue)
+void MicroFenceImpl::gpuSignal(GpuQueueType queue)
 {
 {
 	ANKI_D3D_CHECKF(getGrManagerImpl().getCommandQueue(queue).Signal(m_fence, m_value.fetchAdd(1) + 1));
 	ANKI_D3D_CHECKF(getGrManagerImpl().getCommandQueue(queue).Signal(m_fence, m_value.fetchAdd(1) + 1));
 }
 }
 
 
-void MicroFence::gpuWait(GpuQueueType queue)
+void MicroFenceImpl::gpuWait(GpuQueueType queue)
 {
 {
 	ANKI_D3D_CHECKF(getGrManagerImpl().getCommandQueue(queue).Wait(m_fence, m_value.load()));
 	ANKI_D3D_CHECKF(getGrManagerImpl().getCommandQueue(queue).Wait(m_fence, m_value.load()));
 }
 }
 
 
-Bool MicroFence::clientWait(Second seconds)
-{
-	Bool signaled = false;
-
-	if(seconds == 0.0)
-	{
-		signaled = done();
-	}
-	else
-	{
-		seconds = min<Second>(seconds, g_gpuTimeoutCVar);
-
-		ANKI_D3D_CHECKF(m_fence->SetEventOnCompletion(m_value.load(), m_event));
-
-		const DWORD msec = DWORD(seconds * 1000.0);
-		const DWORD result = WaitForSingleObjectEx(m_event, msec, FALSE);
-
-		if(result == WAIT_OBJECT_0)
-		{
-			signaled = true;
-		}
-		else if(result == WAIT_TIMEOUT)
-		{
-			signaled = false;
-		}
-		else
-		{
-			ANKI_D3D_LOGF("WaitForSingleObjectEx() returned %u", result);
-		}
-	}
-
-	return signaled;
-}
-
-void MicroFencePtrDeleter::operator()(MicroFence* f)
-{
-	FenceFactory::getSingleton().deleteFence(f);
-}
-
-FenceFactory::~FenceFactory()
-{
-	trimSignaledFences(true);
-
-	ANKI_ASSERT(m_fences.getSize() == 0);
-	ANKI_ASSERT(m_aliveFenceCount == 0);
-}
-
-MicroFence* FenceFactory::newFence()
-{
-	MicroFence* out = nullptr;
-
-	{
-		LockGuard<Mutex> lock(m_mtx);
-
-		for(U32 i = 0; i < m_fences.getSize(); ++i)
-		{
-			const Bool signaled = m_fences[i]->done();
-			if(signaled)
-			{
-				out = m_fences[i];
-
-				// Pop it
-				m_fences.erase(m_fences.getBegin() + i);
-				break;
-			}
-		}
-
-		if(out)
-		{
-			// Recycle
-		}
-		else
-		{
-			// Create new
-
-			++m_aliveFenceCount;
-
-			if(m_aliveFenceCount > kMaxAliveFences)
-			{
-				ANKI_D3D_LOGW("Too many alive fences (%u). You may run out of file descriptors", m_aliveFenceCount);
-			}
-		}
-	}
-
-	if(out == nullptr)
-	{
-		// Create a new one
-		out = anki::newInstance<MicroFence>(GrMemoryPool::getSingleton());
-	}
-	else
-	{
-		// Recycle, do nothing
-	}
-
-	ANKI_ASSERT(out->m_refcount.getNonAtomically() == 0);
-	return out;
-}
-
-void FenceFactory::trimSignaledFences(Bool wait)
-{
-	LockGuard<Mutex> lock(m_mtx);
-
-	GrDynamicArray<MicroFence*> unsignaledFences;
-	for(MicroFence* fence : m_fences)
-	{
-		const Bool signaled = fence->clientWait((wait) ? g_gpuTimeoutCVar : 0.0);
-		if(signaled)
-		{
-			deleteInstance(GrMemoryPool::getSingleton(), fence);
-
-			ANKI_ASSERT(m_aliveFenceCount > 0);
-			--m_aliveFenceCount;
-		}
-		else
-		{
-			unsignaledFences.emplaceBack(fence);
-		}
-	}
-
-	m_fences.destroy();
-	if(unsignaledFences.getSize())
-	{
-		m_fences = std::move(unsignaledFences);
-	}
-}
-
-void FenceFactory::deleteFence(MicroFence* fence)
-{
-	ANKI_ASSERT(fence);
-
-	LockGuard<Mutex> lock(m_mtx);
-	m_fences.emplaceBack(fence);
-}
-
 Fence* Fence::newInstance()
 Fence* Fence::newInstance()
 {
 {
 	return anki::newInstance<FenceImpl>(GrMemoryPool::getSingleton(), "N/A");
 	return anki::newInstance<FenceImpl>(GrMemoryPool::getSingleton(), "N/A");

+ 81 - 49
AnKi/Gr/D3D/D3DFence.h

@@ -5,8 +5,9 @@
 
 
 #pragma once
 #pragma once
 
 
-#include <AnKi/Gr/Fence.h>
 #include <AnKi/Gr/D3D/D3DCommon.h>
 #include <AnKi/Gr/D3D/D3DCommon.h>
+#include <AnKi/Gr/Fence.h>
+#include <AnKi/Gr/BackendCommon/MicroFenceFactory.h>
 
 
 namespace anki {
 namespace anki {
 
 
@@ -14,39 +15,47 @@ namespace anki {
 /// @{
 /// @{
 
 
 /// Fence wrapper over D3D fence.
 /// Fence wrapper over D3D fence.
-class MicroFence
+class MicroFenceImpl
 {
 {
-	friend class FenceFactory;
-
 public:
 public:
-	MicroFence();
-
-	MicroFence(const MicroFence&) = delete; // Non-copyable
-
-	~MicroFence()
+	~MicroFenceImpl()
 	{
 	{
-		ANKI_ASSERT(done());
-		if(!CloseHandle(m_event))
-		{
-			ANKI_D3D_LOGE("CloseHandle() failed");
-		}
-		safeRelease(m_fence);
+		ANKI_ASSERT(m_fence == nullptr);
 	}
 	}
 
 
-	MicroFence& operator=(const MicroFence&) = delete; // Non-copyable
-
-	void retain() const
+	explicit operator Bool() const
 	{
 	{
-		m_refcount.fetchAdd(1);
+		return m_fence != nullptr;
 	}
 	}
 
 
-	I32 release() const
+	Bool clientWait(Second seconds)
 	{
 	{
-		return m_refcount.fetchSub(1);
+		ANKI_ASSERT(m_fence && m_event);
+		ANKI_D3D_CHECKF(m_fence->SetEventOnCompletion(m_value.load(), m_event));
+
+		const DWORD msec = DWORD(seconds * 1000.0);
+		const DWORD result = WaitForSingleObjectEx(m_event, msec, FALSE);
+
+		Bool signaled = false;
+		if(result == WAIT_OBJECT_0)
+		{
+			signaled = true;
+		}
+		else if(result == WAIT_TIMEOUT)
+		{
+			signaled = false;
+		}
+		else
+		{
+			ANKI_D3D_LOGF("WaitForSingleObjectEx() returned %u", result);
+		}
+
+		return signaled;
 	}
 	}
 
 
-	Bool done() const
+	Bool signaled() const
 	{
 	{
+		ANKI_ASSERT(m_fence);
 		const U64 cval = m_fence->GetCompletedValue();
 		const U64 cval = m_fence->GetCompletedValue();
 		ANKI_D3D_CHECKF((cval < kMaxU64) ? S_OK : DXGI_ERROR_DEVICE_REMOVED);
 		ANKI_D3D_CHECKF((cval < kMaxU64) ? S_OK : DXGI_ERROR_DEVICE_REMOVED);
 		const U64 val = m_value.load();
 		const U64 val = m_value.load();
@@ -54,61 +63,88 @@ public:
 		return cval == val;
 		return cval == val;
 	}
 	}
 
 
+	void create()
+	{
+		ANKI_ASSERT(m_fence == nullptr && m_event == 0);
+		ANKI_D3D_CHECKF(getDevice().CreateFence(m_value.getNonAtomically(), D3D12_FENCE_FLAG_NONE, IID_PPV_ARGS(&m_fence)));
+		m_event = CreateEvent(nullptr, false, false, nullptr);
+		if(!m_event)
+		{
+			ANKI_D3D_LOGF("CreateEvent() failed");
+		}
+	}
+
+	void destroy()
+	{
+		ANKI_ASSERT(m_fence && m_event);
+		if(!CloseHandle(m_event))
+		{
+			ANKI_D3D_LOGE("CloseHandle() failed");
+		}
+		safeRelease(m_fence);
+		m_fence = nullptr;
+		m_event = 0;
+	}
+
+	void reset()
+	{
+		// Do nothing
+	}
+
+	void setName(CString)
+	{
+		// TODO
+	}
+
 	void gpuSignal(GpuQueueType queue);
 	void gpuSignal(GpuQueueType queue);
 
 
 	void gpuWait(GpuQueueType queue);
 	void gpuWait(GpuQueueType queue);
 
 
-	Bool clientWait(Second seconds);
-
 private:
 private:
 	ID3D12Fence* m_fence = nullptr;
 	ID3D12Fence* m_fence = nullptr;
 	Atomic<U64> m_value = 0;
 	Atomic<U64> m_value = 0;
 	HANDLE m_event = 0;
 	HANDLE m_event = 0;
-	mutable Atomic<I32> m_refcount = {0};
 };
 };
 
 
+using D3DMicroFence = MicroFence<MicroFenceImpl>;
+
 /// Deleter for FencePtr.
 /// Deleter for FencePtr.
 class MicroFencePtrDeleter
 class MicroFencePtrDeleter
 {
 {
 public:
 public:
-	void operator()(MicroFence* f);
+	void operator()(D3DMicroFence* f);
 };
 };
 
 
 /// Fence smart pointer.
 /// Fence smart pointer.
-using MicroFencePtr = IntrusivePtr<MicroFence, MicroFencePtrDeleter>;
+using MicroFencePtr = IntrusivePtr<D3DMicroFence, MicroFencePtrDeleter>;
 
 
 /// A factory of fences.
 /// A factory of fences.
 class FenceFactory : public MakeSingleton<FenceFactory>
 class FenceFactory : public MakeSingleton<FenceFactory>
 {
 {
-	friend class MicroFence;
 	friend class MicroFencePtrDeleter;
 	friend class MicroFencePtrDeleter;
-	friend class Fence;
 
 
 public:
 public:
-	/// Limit the alive fences to avoid having too many file descriptors.
-	static constexpr U32 kMaxAliveFences = 32;
-
-	FenceFactory() = default;
-
-	~FenceFactory();
-
 	/// Create a new fence pointer.
 	/// Create a new fence pointer.
-	MicroFencePtr newInstance()
+	MicroFencePtr newInstance(CString name = "unnamed")
 	{
 	{
-		return MicroFencePtr(newFence());
+		return MicroFencePtr(m_factory.newFence(name));
 	}
 	}
 
 
-	void trimSignaledFences(Bool wait);
-
 private:
 private:
-	GrDynamicArray<MicroFence*> m_fences;
-	U32 m_aliveFenceCount = 0;
-	Mutex m_mtx;
+	MicroFenceFactory<MicroFenceImpl> m_factory;
 
 
-	MicroFence* newFence();
-	void deleteFence(MicroFence* fence);
+	void deleteFence(D3DMicroFence* fence)
+	{
+		m_factory.releaseFence(fence);
+	}
 };
 };
 
 
+inline void MicroFencePtrDeleter::operator()(D3DMicroFence* fence)
+{
+	ANKI_ASSERT(fence);
+	FenceFactory::getSingleton().deleteFence(fence);
+}
+
 /// Fence implementation.
 /// Fence implementation.
 class FenceImpl final : public Fence
 class FenceImpl final : public Fence
 {
 {
@@ -119,10 +155,6 @@ public:
 		: Fence(name)
 		: Fence(name)
 	{
 	{
 	}
 	}
-
-	~FenceImpl()
-	{
-	}
 };
 };
 /// @}
 /// @}
 
 

+ 6 - 91
AnKi/Gr/D3D/D3DFrameGarbageCollector.cpp

@@ -7,104 +7,19 @@
 
 
 namespace anki {
 namespace anki {
 
 
-FrameGarbageCollector::~FrameGarbageCollector()
+TextureGarbage::~TextureGarbage()
 {
 {
-	collectGarbage();
-	ANKI_ASSERT(m_frames.isEmpty());
-}
-
-void FrameGarbageCollector::endFrame(MicroFence* frameFence)
-{
-	LockGuard lock(m_mtx);
-
-	if(!m_frames.isEmpty() && !m_frames.getBack().m_fence.isCreated())
+	for(U32 i = 0; i < m_descriptorHeapHandles.getSize(); ++i)
 	{
 	{
-		// Last frame is without a fence, asign the fence to not have it garbage collected
-		m_frames.getBack().m_fence.reset(frameFence);
+		DescriptorFactory::getSingleton().freePersistent(m_descriptorHeapHandles[i]);
 	}
 	}
 
 
-	collectGarbage();
-}
-
-FrameGarbageCollector::FrameGarbage& FrameGarbageCollector::getFrame()
-{
-	if(!m_frames.isEmpty() && !m_frames.getBack().m_fence.isCreated())
-	{
-		// Do nothing
-	}
-	else
-	{
-		FrameGarbage* newGarbage = newInstance<FrameGarbage>(GrMemoryPool::getSingleton());
-		m_frames.pushBack(newGarbage);
-	}
-
-	return m_frames.getBack();
-}
-
-void FrameGarbageCollector::newTextureGarbage(TextureGarbage* textureGarbage)
-{
-	LockGuard<Mutex> lock(m_mtx);
-	FrameGarbage& frame = getFrame();
-	frame.m_textureGarbage.pushBack(textureGarbage);
-}
-
-void FrameGarbageCollector::newBufferGarbage(BufferGarbage* garbage)
-{
-	LockGuard<Mutex> lock(m_mtx);
-	FrameGarbage& frame = getFrame();
-	frame.m_bufferGarbage.pushBack(garbage);
+	safeRelease(m_resource);
 }
 }
 
 
-void FrameGarbageCollector::collectGarbage()
+BufferGarbage::~BufferGarbage()
 {
 {
-	if(m_frames.isEmpty()) [[likely]]
-	{
-		return;
-	}
-
-	IntrusiveList<FrameGarbage> newFrames;
-	while(!m_frames.isEmpty())
-	{
-		FrameGarbage& frame = *m_frames.popFront();
-
-		if(frame.m_fence.isCreated() && !frame.m_fence->done())
-		{
-			ANKI_ASSERT(!frame.m_textureGarbage.isEmpty() || !frame.m_bufferGarbage.isEmpty());
-			newFrames.pushBack(&frame);
-			continue;
-		}
-
-		// Frame is done, dispose garbage and destroy it
-
-		// Dispose texture garbage
-		while(!frame.m_textureGarbage.isEmpty())
-		{
-			TextureGarbage* textureGarbage = frame.m_textureGarbage.popBack();
-
-			for(U32 i = 0; i < textureGarbage->m_descriptorHeapHandles.getSize(); ++i)
-			{
-				DescriptorFactory::getSingleton().freePersistent(textureGarbage->m_descriptorHeapHandles[i]);
-			}
-
-			safeRelease(textureGarbage->m_resource);
-
-			deleteInstance(GrMemoryPool::getSingleton(), textureGarbage);
-		}
-
-		// Dispose buffer garbage
-		while(!frame.m_bufferGarbage.isEmpty())
-		{
-			BufferGarbage* garbage = frame.m_bufferGarbage.popBack();
-
-			safeRelease(garbage->m_resource);
-
-			deleteInstance(GrMemoryPool::getSingleton(), garbage);
-		}
-
-		deleteInstance(GrMemoryPool::getSingleton(), &frame);
-	}
-
-	m_frames = std::move(newFrames);
+	safeRelease(m_resource);
 }
 }
 
 
 } // end namespace anki
 } // end namespace anki

+ 12 - 38
AnKi/Gr/D3D/D3DFrameGarbageCollector.h

@@ -6,8 +6,7 @@
 #pragma once
 #pragma once
 
 
 #include <AnKi/Gr/D3D/D3DDescriptor.h>
 #include <AnKi/Gr/D3D/D3DDescriptor.h>
-#include <AnKi/Gr/D3D/D3DFence.h>
-#include <AnKi/Util/List.h>
+#include <AnKi/Gr/BackendCommon/FrameGarbageCollector.h>
 
 
 namespace anki {
 namespace anki {
 
 
@@ -20,6 +19,8 @@ class TextureGarbage : public IntrusiveListEnabled<TextureGarbage>
 public:
 public:
 	GrDynamicArray<DescriptorHeapHandle> m_descriptorHeapHandles;
 	GrDynamicArray<DescriptorHeapHandle> m_descriptorHeapHandles;
 	ID3D12Resource* m_resource = nullptr;
 	ID3D12Resource* m_resource = nullptr;
+
+	~TextureGarbage();
 };
 };
 
 
 /// @memberof FrameGarbageCollector
 /// @memberof FrameGarbageCollector
@@ -27,46 +28,19 @@ class BufferGarbage : public IntrusiveListEnabled<BufferGarbage>
 {
 {
 public:
 public:
 	ID3D12Resource* m_resource = nullptr;
 	ID3D12Resource* m_resource = nullptr;
+
+	~BufferGarbage();
 };
 };
 
 
-/// This class gathers various garbages and disposes them when in some later frame where it is safe to do so. This is used on bindless textures and
-/// buffers where we have to wait until the frame where they were deleted is done.
-class FrameGarbageCollector : public MakeSingleton<FrameGarbageCollector>
+/// @memberof FrameGarbageCollector
+class ASGarbage : public IntrusiveListEnabled<ASGarbage>
 {
 {
-public:
-	FrameGarbageCollector() = default;
-
-	FrameGarbageCollector(const FrameGarbageCollector&) = delete;
-
-	~FrameGarbageCollector();
-
-	FrameGarbageCollector& operator=(const FrameGarbageCollector&) = delete;
-
-	/// @note It's thread-safe.
-	void newTextureGarbage(TextureGarbage* textureGarbage);
-
-	/// @note It's thread-safe.
-	void newBufferGarbage(BufferGarbage* garbage);
-
-	/// Finalizes a frame.
-	/// @note It's thread-safe.
-	void endFrame(MicroFence* frameFence);
-
-private:
-	class FrameGarbage : public IntrusiveListEnabled<FrameGarbage>
-	{
-	public:
-		IntrusiveList<TextureGarbage> m_textureGarbage;
-		IntrusiveList<BufferGarbage> m_bufferGarbage;
-		MicroFencePtr m_fence;
-	};
-
-	Mutex m_mtx;
-	IntrusiveList<FrameGarbage> m_frames;
-
-	void collectGarbage();
+};
 
 
-	FrameGarbage& getFrame();
+class D3DFrameGarbageCollector :
+	public FrameGarbageCollector<TextureGarbage, BufferGarbage, ASGarbage>,
+	public MakeSingleton<D3DFrameGarbageCollector>
+{
 };
 };
 /// @}
 /// @}
 
 

+ 104 - 60
AnKi/Gr/D3D/D3DGrManager.cpp

@@ -108,55 +108,34 @@ Error GrManager::init(GrManagerInitInfo& inf)
 	return self.initInternal(inf);
 	return self.initInternal(inf);
 }
 }
 
 
-TexturePtr GrManager::acquireNextPresentableTexture()
+void GrManager::beginFrame()
 {
 {
 	ANKI_D3D_SELF(GrManagerImpl);
 	ANKI_D3D_SELF(GrManagerImpl);
-
-	self.m_crntSwapchain->m_backbufferIdx = self.m_crntSwapchain->m_swapchain->GetCurrentBackBufferIndex();
-	return self.m_crntSwapchain->m_textures[self.m_crntSwapchain->m_backbufferIdx];
+	self.beginFrameInternal();
 }
 }
 
 
-void GrManager::swapBuffers()
+TexturePtr GrManager::acquireNextPresentableTexture()
 {
 {
-	ANKI_TRACE_SCOPED_EVENT(D3DSwapBuffers);
 	ANKI_D3D_SELF(GrManagerImpl);
 	ANKI_D3D_SELF(GrManagerImpl);
+	return self.acquireNextPresentableTextureInternal();
+}
 
 
-	self.m_crntSwapchain->m_swapchain->Present((g_vsyncCVar) ? 1 : 0, (g_vsyncCVar) ? 0 : DXGI_PRESENT_ALLOW_TEARING);
-
-	MicroFencePtr presentFence = FenceFactory::getSingleton().newInstance();
-	presentFence->gpuSignal(GpuQueueType::kGeneral);
-
-	auto& crntFrame = self.m_frames[self.m_crntFrame];
-
-	if(crntFrame.m_presentFence) [[likely]]
-	{
-		// Wait prev fence
-		const Bool signaled = crntFrame.m_presentFence->clientWait(kMaxSecond);
-		if(!signaled)
-		{
-			ANKI_D3D_LOGF("Frame fence didn't signal");
-		}
-	}
-
-	crntFrame.m_presentFence = presentFence;
-
-	self.m_crntFrame = (self.m_crntFrame + 1) % self.m_frames.getSize();
-
-	FrameGarbageCollector::getSingleton().endFrame(presentFence.get());
-	DescriptorFactory::getSingleton().endFrame();
+void GrManager::endFrame()
+{
+	ANKI_D3D_SELF(GrManagerImpl);
+	self.endFrameInternal();
 }
 }
 
 
 void GrManager::finish()
 void GrManager::finish()
 {
 {
-	if(FenceFactory::isAllocated())
-	{
-		for(GpuQueueType qType : EnumIterable<GpuQueueType>())
-		{
-			MicroFencePtr fence = FenceFactory::getSingleton().newInstance();
-			fence->gpuSignal(qType);
-			fence->clientWait(kMaxSecond);
-		}
-	}
+	ANKI_D3D_SELF(GrManagerImpl);
+	self.finishInternal();
+}
+
+void GrManager::submit(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFences, FencePtr* signalFence)
+{
+	ANKI_D3D_SELF(GrManagerImpl);
+	self.submitInternal(cmdbs, waitFences, signalFence);
 }
 }
 
 
 #define ANKI_NEW_GR_OBJECT(type) \
 #define ANKI_NEW_GR_OBJECT(type) \
@@ -201,12 +180,74 @@ GrManagerImpl::~GrManagerImpl()
 	destroy();
 	destroy();
 }
 }
 
 
-void GrManager::submit(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFences, FencePtr* signalFence)
+void GrManagerImpl::beginFrameInternal()
 {
 {
-	ANKI_D3D_SELF(GrManagerImpl);
+	ANKI_TRACE_FUNCTION();
+
+	LockGuard<Mutex> lock(m_globalMtx);
+
+	m_crntFrame = (m_crntFrame + 1) % m_frames.getSize();
+
+	// Wait for the oldest frame because we don't want to start the new one too early
+	PerFrame& frame = m_frames[m_crntFrame];
+
+	for(MicroFencePtr& fence : frame.m_fences)
+	{
+		if(fence)
+		{
+			const Bool signaled = fence->clientWait(kMaxSecond);
+			if(!signaled)
+			{
+				ANKI_D3D_LOGF("Timeout detected");
+			}
+		}
+	}
+
+	frame.m_fences.destroy();
+
+	// Reset the rest of the frame (after the fences)
+	frame.m_presentFence.reset(nullptr);
+	frame.m_queueWroteToSwapchainImage = GpuQueueType::kCount;
+
+	// Clear garbage
+	D3DFrameGarbageCollector::getSingleton().beginFrameAndCollectGarbage();
+}
+
+TexturePtr GrManagerImpl::acquireNextPresentableTextureInternal()
+{
+	ANKI_TRACE_FUNCTION();
+
+	m_crntSwapchain->m_backbufferIdx = m_crntSwapchain->m_swapchain->GetCurrentBackBufferIndex();
+	return m_crntSwapchain->m_textures[m_crntSwapchain->m_backbufferIdx];
+}
+
+void GrManagerImpl::endFrameInternal()
+{
+	ANKI_TRACE_FUNCTION();
+
+	m_crntSwapchain->m_swapchain->Present((g_vsyncCVar) ? 1 : 0, (g_vsyncCVar) ? 0 : DXGI_PRESENT_ALLOW_TEARING);
+
+	MicroFencePtr presentFence = FenceFactory::getSingleton().newInstance("Present");
+	presentFence->getImplementation().gpuSignal(GpuQueueType::kGeneral); // Only general can present
+
+	m_crntSwapchain->setFence(presentFence.get());
+
+	LockGuard lock(m_globalMtx);
+
+	auto& crntFrame = m_frames[m_crntFrame];
+
+	crntFrame.m_presentFence = presentFence;
+	crntFrame.m_fences.emplaceBack(presentFence);
+
+	DescriptorFactory::getSingleton().endFrame();
+}
+
+void GrManagerImpl::submitInternal(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFences, FencePtr* signalFence)
+{
+	ANKI_TRACE_FUNCTION();
 
 
 	// First thing, create a fence
 	// First thing, create a fence
-	MicroFencePtr fence = FenceFactory::getSingleton().newInstance();
+	MicroFencePtr fence = FenceFactory::getSingleton().newInstance("Submit");
 
 
 	// Gather command lists
 	// Gather command lists
 	GrDynamicArray<ID3D12CommandList*> d3dCmdLists;
 	GrDynamicArray<ID3D12CommandList*> d3dCmdLists;
@@ -235,14 +276,14 @@ void GrManager::submit(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFe
 	for(Fence* fence : waitFences)
 	for(Fence* fence : waitFences)
 	{
 	{
 		FenceImpl& impl = static_cast<FenceImpl&>(*fence);
 		FenceImpl& impl = static_cast<FenceImpl&>(*fence);
-		impl.m_fence->gpuWait(queueType);
+		impl.m_fence->getImplementation().gpuWait(queueType);
 	}
 	}
 
 
 	// Submit command lists
 	// Submit command lists
-	self.m_queues[queueType]->ExecuteCommandLists(d3dCmdLists.getSize(), d3dCmdLists.getBegin());
+	m_queues[queueType]->ExecuteCommandLists(d3dCmdLists.getSize(), d3dCmdLists.getBegin());
 
 
 	// Signal fence
 	// Signal fence
-	fence->gpuSignal(queueType);
+	fence->getImplementation().gpuSignal(queueType);
 
 
 	if(signalFence)
 	if(signalFence)
 	{
 	{
@@ -250,6 +291,22 @@ void GrManager::submit(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFe
 		fenceImpl->m_fence = fence;
 		fenceImpl->m_fence = fence;
 		signalFence->reset(fenceImpl);
 		signalFence->reset(fenceImpl);
 	}
 	}
+
+	LockGuard lock(m_globalMtx);
+	m_frames[m_crntFrame].m_fences.emplaceBack(fence.get());
+}
+
+void GrManagerImpl::finishInternal()
+{
+	if(FenceFactory::isAllocated())
+	{
+		for(GpuQueueType qType : EnumIterable<GpuQueueType>())
+		{
+			MicroFencePtr fence = FenceFactory::getSingleton().newInstance();
+			fence->getImplementation().gpuSignal(qType);
+			fence->clientWait(kMaxSecond);
+		}
+	}
 }
 }
 
 
 Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
 Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
@@ -463,7 +520,7 @@ Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
 	RootSignatureFactory::allocateSingleton();
 	RootSignatureFactory::allocateSingleton();
 	FenceFactory::allocateSingleton();
 	FenceFactory::allocateSingleton();
 	CommandBufferFactory::allocateSingleton();
 	CommandBufferFactory::allocateSingleton();
-	FrameGarbageCollector::allocateSingleton();
+	D3DFrameGarbageCollector::allocateSingleton();
 
 
 	IndirectCommandSignatureFactory::allocateSingleton();
 	IndirectCommandSignatureFactory::allocateSingleton();
 	ANKI_CHECK(IndirectCommandSignatureFactory::getSingleton().init());
 	ANKI_CHECK(IndirectCommandSignatureFactory::getSingleton().init());
@@ -492,7 +549,7 @@ void GrManagerImpl::destroy()
 
 
 	m_zeroBuffer.reset(nullptr);
 	m_zeroBuffer.reset(nullptr);
 
 
-	waitAllQueues();
+	finishInternal();
 
 
 	// Cleanup self
 	// Cleanup self
 	m_crntSwapchain.reset(nullptr);
 	m_crntSwapchain.reset(nullptr);
@@ -503,7 +560,7 @@ void GrManagerImpl::destroy()
 	PrimitivesPassedClippingFactory::freeSingleton();
 	PrimitivesPassedClippingFactory::freeSingleton();
 	TimestampQueryFactory::freeSingleton();
 	TimestampQueryFactory::freeSingleton();
 	SwapchainFactory::freeSingleton();
 	SwapchainFactory::freeSingleton();
-	FrameGarbageCollector::freeSingleton();
+	D3DFrameGarbageCollector::freeSingleton();
 	RootSignatureFactory::freeSingleton();
 	RootSignatureFactory::freeSingleton();
 	DescriptorFactory::freeSingleton();
 	DescriptorFactory::freeSingleton();
 	IndirectCommandSignatureFactory::freeSingleton();
 	IndirectCommandSignatureFactory::freeSingleton();
@@ -525,19 +582,6 @@ void GrManagerImpl::destroy()
 	GrMemoryPool::freeSingleton();
 	GrMemoryPool::freeSingleton();
 }
 }
 
 
-void GrManagerImpl::waitAllQueues()
-{
-	if(FenceFactory::isAllocated())
-	{
-		for(GpuQueueType queueType : EnumIterable<GpuQueueType>())
-		{
-			MicroFencePtr fence = FenceFactory::getSingleton().newInstance();
-			fence->gpuSignal(queueType);
-			fence->clientWait(kMaxSecond);
-		}
-	}
-}
-
 void GrManagerImpl::invokeDred() const
 void GrManagerImpl::invokeDred() const
 {
 {
 	Bool error = false;
 	Bool error = false;

+ 10 - 1
AnKi/Gr/D3D/D3DGrManager.h

@@ -77,6 +77,7 @@ private:
 	class PerFrame
 	class PerFrame
 	{
 	{
 	public:
 	public:
+		GrDynamicArray<MicroFencePtr> m_fences;
 		MicroFencePtr m_presentFence;
 		MicroFencePtr m_presentFence;
 
 
 		GpuQueueType m_queueWroteToSwapchainImage = GpuQueueType::kCount;
 		GpuQueueType m_queueWroteToSwapchainImage = GpuQueueType::kCount;
@@ -95,7 +96,15 @@ private:
 
 
 	void destroy();
 	void destroy();
 
 
-	void waitAllQueues();
+	TexturePtr acquireNextPresentableTextureInternal();
+
+	void beginFrameInternal();
+
+	void endFrameInternal();
+
+	void submitInternal(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFences, FencePtr* signalFence);
+
+	void finishInternal();
 };
 };
 /// @}
 /// @}
 
 

+ 3 - 3
AnKi/Gr/D3D/D3DQueryFactory.cpp

@@ -81,7 +81,7 @@ void QueryFactory::deleteQuery(QueryHandle& handle)
 
 
 	if(chunkIt->m_fenceArr[handle.m_queryIndex].isCreated())
 	if(chunkIt->m_fenceArr[handle.m_queryIndex].isCreated())
 	{
 	{
-		ANKI_ASSERT(chunkIt->m_fenceArr[handle.m_queryIndex]->done() && "Trying to delete while the query is in flight");
+		ANKI_ASSERT(chunkIt->m_fenceArr[handle.m_queryIndex]->signaled() && "Trying to delete while the query is in flight");
 		chunkIt->m_fenceArr[handle.m_queryIndex].reset(nullptr);
 		chunkIt->m_fenceArr[handle.m_queryIndex].reset(nullptr);
 	}
 	}
 
 
@@ -108,7 +108,7 @@ Bool QueryFactory::getResult(QueryHandle handle, U64& result)
 	auto it = m_chunkArray.indexToIterator(handle.m_chunkIndex);
 	auto it = m_chunkArray.indexToIterator(handle.m_chunkIndex);
 	ANKI_ASSERT(it->m_queryWritten.get(handle.m_queryIndex) && "Trying to get the result of a query that wasn't written");
 	ANKI_ASSERT(it->m_queryWritten.get(handle.m_queryIndex) && "Trying to get the result of a query that wasn't written");
 
 
-	const Bool available = (it->m_fenceArr[handle.m_queryIndex].isCreated()) ? it->m_fenceArr[handle.m_queryIndex]->done() : true;
+	const Bool available = (it->m_fenceArr[handle.m_queryIndex].isCreated()) ? it->m_fenceArr[handle.m_queryIndex]->signaled() : true;
 	if(available)
 	if(available)
 	{
 	{
 		result = it->m_resultsBufferCpuAddr[(handle.m_queryIndex * m_resultStructSize + m_resultMemberOffset) / sizeof(U64)];
 		result = it->m_resultsBufferCpuAddr[(handle.m_queryIndex * m_resultStructSize + m_resultMemberOffset) / sizeof(U64)];
@@ -122,7 +122,7 @@ Bool QueryFactory::getResult(QueryHandle handle, U64& result)
 	return available;
 	return available;
 }
 }
 
 
-void QueryFactory::postSubmitWork(QueryHandle handle, MicroFence* fence)
+void QueryFactory::postSubmitWork(QueryHandle handle, D3DMicroFence* fence)
 {
 {
 	ANKI_ASSERT(handle.isValid() && handle.m_type == m_type && fence);
 	ANKI_ASSERT(handle.isValid() && handle.m_type == m_type && fence);
 
 

+ 1 - 1
AnKi/Gr/D3D/D3DQueryFactory.h

@@ -86,7 +86,7 @@ public:
 	}
 	}
 
 
 	/// Call this on submit if the query was written.
 	/// Call this on submit if the query was written.
-	void postSubmitWork(QueryHandle handle, MicroFence* fence);
+	void postSubmitWork(QueryHandle handle, D3DMicroFence* fence);
 
 
 private:
 private:
 	class Chunk
 	class Chunk

+ 2 - 2
AnKi/Gr/D3D/D3DSwapchainFactory.h

@@ -48,12 +48,12 @@ public:
 		return m_refcount.load();
 		return m_refcount.load();
 	}
 	}
 
 
-	void setFence(MicroFence* fence)
+	void setFence(D3DMicroFence* fence)
 	{
 	{
 		m_fence.reset(fence);
 		m_fence.reset(fence);
 	}
 	}
 
 
-	MicroFence* getFence() const
+	D3DMicroFence* getFence() const
 	{
 	{
 		return m_fence.tryGet();
 		return m_fence.tryGet();
 	}
 	}

+ 1 - 1
AnKi/Gr/D3D/D3DTexture.cpp

@@ -73,7 +73,7 @@ TextureImpl::~TextureImpl()
 		garbage->m_resource = m_resource;
 		garbage->m_resource = m_resource;
 	}
 	}
 
 
-	FrameGarbageCollector::getSingleton().newTextureGarbage(garbage);
+	D3DFrameGarbageCollector::getSingleton().newTextureGarbage(garbage);
 }
 }
 
 
 Error TextureImpl::initInternal(ID3D12Resource* external, const TextureInitInfo& init)
 Error TextureImpl::initInternal(ID3D12Resource* external, const TextureInitInfo& init)

+ 5 - 2
AnKi/Gr/GrManager.h

@@ -43,12 +43,15 @@ public:
 		return m_capabilities;
 		return m_capabilities;
 	}
 	}
 
 
+	/// First call in the frame. Do that before everything else.
+	void beginFrame();
+
 	/// Get next presentable image. The returned Texture is valid until the following swapBuffers. After that it might
 	/// Get next presentable image. The returned Texture is valid until the following swapBuffers. After that it might
 	/// dissapear even if you hold the reference.
 	/// dissapear even if you hold the reference.
 	TexturePtr acquireNextPresentableTexture();
 	TexturePtr acquireNextPresentableTexture();
 
 
-	/// Swap buffers
-	void swapBuffers();
+	/// End this frame.
+	void endFrame();
 
 
 	/// Wait for all work to finish.
 	/// Wait for all work to finish.
 	void finish();
 	void finish();

+ 1 - 1
AnKi/Gr/Vulkan/VkAccelerationStructure.cpp

@@ -34,7 +34,7 @@ AccelerationStructureImpl::~AccelerationStructureImpl()
 	{
 	{
 		ASGarbage* garbage = anki::newInstance<ASGarbage>(GrMemoryPool::getSingleton());
 		ASGarbage* garbage = anki::newInstance<ASGarbage>(GrMemoryPool::getSingleton());
 		garbage->m_asHandle = m_handle;
 		garbage->m_asHandle = m_handle;
-		getGrManagerImpl().getFrameGarbageCollector().newASGarbage(garbage);
+		VulkanFrameGarbageCollector::getSingleton().newASGarbage(garbage);
 	}
 	}
 }
 }
 
 

+ 1 - 1
AnKi/Gr/Vulkan/VkBuffer.cpp

@@ -108,7 +108,7 @@ BufferImpl::~BufferImpl()
 		}
 		}
 	}
 	}
 
 
-	getGrManagerImpl().getFrameGarbageCollector().newBufferGarbage(garbage);
+	VulkanFrameGarbageCollector::getSingleton().newBufferGarbage(garbage);
 
 
 #if ANKI_ASSERTIONS_ENABLED
 #if ANKI_ASSERTIONS_ENABLED
 	if(m_needsFlush && m_flushCount.load() == 0)
 	if(m_needsFlush && m_flushCount.load() == 0)

+ 1 - 1
AnKi/Gr/Vulkan/VkCommandBuffer.h

@@ -44,7 +44,7 @@ public:
 
 
 	Error init(const CommandBufferInitInfo& init);
 	Error init(const CommandBufferInitInfo& init);
 
 
-	void setFence(MicroFence* fence)
+	void setFence(VulkanMicroFence* fence)
 	{
 	{
 		m_microCmdb->setFence(fence);
 		m_microCmdb->setFence(fence);
 	}
 	}

+ 1 - 1
AnKi/Gr/Vulkan/VkCommandBufferFactory.cpp

@@ -10,7 +10,7 @@
 
 
 namespace anki {
 namespace anki {
 
 
-static StatCounter g_commandBufferCountStatVar(StatCategory::kMisc, "CommandBufferCount", StatFlag::kNone);
+static StatCounter g_commandBufferCountStatVar(StatCategory::kGr, "CommandBufferCount", StatFlag::kNone);
 
 
 void MicroCommandBufferPtrDeleter::operator()(MicroCommandBuffer* ptr)
 void MicroCommandBufferPtrDeleter::operator()(MicroCommandBuffer* ptr)
 {
 {

+ 2 - 2
AnKi/Gr/Vulkan/VkCommandBufferFactory.h

@@ -52,12 +52,12 @@ public:
 		return m_refcount.load();
 		return m_refcount.load();
 	}
 	}
 
 
-	void setFence(MicroFence* fence)
+	void setFence(VulkanMicroFence* fence)
 	{
 	{
 		m_fence.reset(fence);
 		m_fence.reset(fence);
 	}
 	}
 
 
-	MicroFence* getFence() const
+	VulkanMicroFence* getFence() const
 	{
 	{
 		return m_fence.tryGet();
 		return m_fence.tryGet();
 	}
 	}

+ 2 - 2
AnKi/Gr/Vulkan/VkDescriptor.cpp

@@ -10,8 +10,8 @@
 
 
 namespace anki {
 namespace anki {
 
 
-static StatCounter g_descriptorSetsAllocatedStatVar(StatCategory::kMisc, "DescriptorSets allocated this frame", StatFlag::kZeroEveryFrame);
-static StatCounter g_descriptorSetsWrittenStatVar(StatCategory::kMisc, "DescriptorSets written this frame", StatFlag::kZeroEveryFrame);
+static StatCounter g_descriptorSetsAllocatedStatVar(StatCategory::kGr, "DescriptorSets allocated this frame", StatFlag::kZeroEveryFrame);
+static StatCounter g_descriptorSetsWrittenStatVar(StatCategory::kGr, "DescriptorSets written this frame", StatFlag::kZeroEveryFrame);
 
 
 /// Contains some constants. It's a class to avoid bugs initializing arrays (m_descriptorCount).
 /// Contains some constants. It's a class to avoid bugs initializing arrays (m_descriptorCount).
 class DSAllocatorConstants
 class DSAllocatorConstants

+ 4 - 104
AnKi/Gr/Vulkan/VkFenceFactory.cpp

@@ -4,114 +4,14 @@
 // http://www.anki3d.org/LICENSE
 // http://www.anki3d.org/LICENSE
 
 
 #include <AnKi/Gr/Vulkan/VkFenceFactory.h>
 #include <AnKi/Gr/Vulkan/VkFenceFactory.h>
+#include <AnKi/Gr/Vulkan/VkGrManager.h>
 
 
 namespace anki {
 namespace anki {
 
 
-void MicroFencePtrDeleter::operator()(MicroFence* f)
+void MicroFenceImpl::setName(CString name) const
 {
 {
-	ANKI_ASSERT(f);
-	FenceFactory::getSingleton().deleteFence(f);
-}
-
-FenceFactory::~FenceFactory()
-{
-	trimSignaledFences(true);
-
-	ANKI_ASSERT(m_fences.getSize() == 0);
-	ANKI_ASSERT(m_aliveFenceCount == 0);
-}
-
-MicroFence* FenceFactory::newFence()
-{
-	MicroFence* out = nullptr;
-
-	{
-		LockGuard<Mutex> lock(m_mtx);
-
-		for(U32 i = 0; i < m_fences.getSize(); ++i)
-		{
-			VkResult status;
-			ANKI_VK_CHECKF(status = vkGetFenceStatus(getVkDevice(), m_fences[i]->getHandle()));
-			if(status == VK_SUCCESS)
-			{
-				out = m_fences[i];
-
-				// Pop it
-				m_fences.erase(m_fences.getBegin() + i);
-				break;
-			}
-			else if(status != VK_NOT_READY)
-			{
-				ANKI_ASSERT(0);
-			}
-		}
-
-		if(out)
-		{
-			// Recycle
-		}
-		else
-		{
-			// Create new
-
-			++m_aliveFenceCount;
-
-			if(m_aliveFenceCount > kMaxAliveFences)
-			{
-				ANKI_VK_LOGW("Too many alive fences (%u). You may run out of file descriptors", m_aliveFenceCount);
-			}
-		}
-	}
-
-	if(out == nullptr)
-	{
-		// Create a new one
-		out = anki::newInstance<MicroFence>(GrMemoryPool::getSingleton());
-	}
-	else
-	{
-		// Recycle
-		ANKI_VK_CHECKF(vkResetFences(getVkDevice(), 1, &out->getHandle()));
-	}
-
-	ANKI_ASSERT(out->m_refcount.getNonAtomically() == 0);
-	return out;
-}
-
-void FenceFactory::deleteFence(MicroFence* fence)
-{
-	ANKI_ASSERT(fence);
-
-	LockGuard<Mutex> lock(m_mtx);
-	m_fences.emplaceBack(fence);
-}
-
-void FenceFactory::trimSignaledFences(Bool wait)
-{
-	LockGuard<Mutex> lock(m_mtx);
-
-	GrDynamicArray<MicroFence*> unsignaledFences;
-	for(MicroFence* fence : m_fences)
-	{
-		const Bool signaled = fence->clientWait((wait) ? g_gpuTimeoutCVar : 0.0);
-		if(signaled)
-		{
-			deleteInstance(GrMemoryPool::getSingleton(), fence);
-
-			ANKI_ASSERT(m_aliveFenceCount > 0);
-			--m_aliveFenceCount;
-		}
-		else
-		{
-			unsignaledFences.emplaceBack(fence);
-		}
-	}
-
-	m_fences.destroy();
-	if(unsignaledFences.getSize())
-	{
-		m_fences = std::move(unsignaledFences);
-	}
+	ANKI_ASSERT(m_handle);
+	getGrManagerImpl().trySetVulkanHandleName(name, VK_OBJECT_TYPE_FENCE, m_handle);
 }
 }
 
 
 } // end namespace anki
 } // end namespace anki

+ 47 - 77
AnKi/Gr/Vulkan/VkFenceFactory.h

@@ -6,6 +6,7 @@
 #pragma once
 #pragma once
 
 
 #include <AnKi/Gr/Vulkan/VkCommon.h>
 #include <AnKi/Gr/Vulkan/VkCommon.h>
+#include <AnKi/Gr/BackendCommon/MicroFenceFactory.h>
 #include <AnKi/Util/Tracer.h>
 #include <AnKi/Util/Tracer.h>
 
 
 namespace anki {
 namespace anki {
@@ -14,133 +15,102 @@ namespace anki {
 /// @{
 /// @{
 
 
 /// Fence wrapper over VkFence.
 /// Fence wrapper over VkFence.
-class MicroFence
+class MicroFenceImpl
 {
 {
-	friend class FenceFactory;
-	friend class MicroFencePtrDeleter;
-
 public:
 public:
-	MicroFence()
-	{
-		VkFenceCreateInfo ci = {};
-		ci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+	VkFence m_handle = VK_NULL_HANDLE;
 
 
-		ANKI_TRACE_INC_COUNTER(VkFenceCreate, 1);
-		ANKI_VK_CHECKF(vkCreateFence(getVkDevice(), &ci, nullptr, &m_handle));
+	~MicroFenceImpl()
+	{
+		ANKI_ASSERT(!m_handle);
 	}
 	}
 
 
-	MicroFence(const MicroFence&) = delete; // Non-copyable
-
-	~MicroFence()
+	operator Bool() const
 	{
 	{
-		if(m_handle)
-		{
-			ANKI_ASSERT(done());
-			vkDestroyFence(getVkDevice(), m_handle, nullptr);
-		}
+		return m_handle != 0;
 	}
 	}
 
 
-	MicroFence& operator=(const MicroFence&) = delete; // Non-copyable
-
-	const VkFence& getHandle() const
+	Bool clientWait(Second seconds)
 	{
 	{
 		ANKI_ASSERT(m_handle);
 		ANKI_ASSERT(m_handle);
-		return m_handle;
-	}
+		const F64 nsf = 1e+9 * seconds;
+		const U64 ns = U64(nsf);
+		VkResult res;
+		ANKI_VK_CHECKF(res = vkWaitForFences(getVkDevice(), 1, &m_handle, true, ns));
 
 
-	void retain() const
-	{
-		m_refcount.fetchAdd(1);
+		return res != VK_TIMEOUT;
 	}
 	}
 
 
-	I32 release() const
+	Bool signaled()
 	{
 	{
-		return m_refcount.fetchSub(1);
+		ANKI_ASSERT(m_handle);
+		VkResult status;
+		ANKI_VK_CHECKF(status = vkGetFenceStatus(getVkDevice(), m_handle));
+		return status == VK_SUCCESS;
 	}
 	}
 
 
-	void wait()
+	void create()
 	{
 	{
-		const Bool timeout = !clientWait(kMaxSecond);
-		if(timeout) [[unlikely]]
-		{
-			ANKI_VK_LOGF("Waiting for a fence timed out");
-		}
+		ANKI_ASSERT(!m_handle);
+		VkFenceCreateInfo ci = {};
+		ci.sType = VK_STRUCTURE_TYPE_FENCE_CREATE_INFO;
+		ANKI_VK_CHECKF(vkCreateFence(getVkDevice(), &ci, nullptr, &m_handle));
 	}
 	}
 
 
-	Bool clientWait(Second seconds)
+	void destroy()
 	{
 	{
 		ANKI_ASSERT(m_handle);
 		ANKI_ASSERT(m_handle);
-
-		if(seconds == 0.0)
-		{
-			return done();
-		}
-		else
-		{
-			seconds = min<Second>(seconds, g_gpuTimeoutCVar);
-			const F64 nsf = 1e+9 * seconds;
-			const U64 ns = U64(nsf);
-			VkResult res;
-			ANKI_VK_CHECKF(res = vkWaitForFences(getVkDevice(), 1, &m_handle, true, ns));
-
-			return res != VK_TIMEOUT;
-		}
+		vkDestroyFence(getVkDevice(), m_handle, nullptr);
+		m_handle = 0;
 	}
 	}
 
 
-	Bool done() const
+	void reset()
 	{
 	{
 		ANKI_ASSERT(m_handle);
 		ANKI_ASSERT(m_handle);
-
-		VkResult status;
-		ANKI_VK_CHECKF(status = vkGetFenceStatus(getVkDevice(), m_handle));
-		return status == VK_SUCCESS;
+		ANKI_VK_CHECKF(vkResetFences(getVkDevice(), 1, &m_handle));
 	}
 	}
 
 
-private:
-	VkFence m_handle = VK_NULL_HANDLE;
-	mutable Atomic<I32> m_refcount = {0};
+	void setName(CString name) const;
 };
 };
 
 
+using VulkanMicroFence = MicroFence<MicroFenceImpl>;
+
 /// Deleter for FencePtr.
 /// Deleter for FencePtr.
 class MicroFencePtrDeleter
 class MicroFencePtrDeleter
 {
 {
 public:
 public:
-	void operator()(MicroFence* f);
+	void operator()(VulkanMicroFence* fence);
 };
 };
 
 
 /// Fence smart pointer.
 /// Fence smart pointer.
-using MicroFencePtr = IntrusivePtr<MicroFence, MicroFencePtrDeleter>;
+using MicroFencePtr = IntrusivePtr<VulkanMicroFence, MicroFencePtrDeleter>;
 
 
 /// A factory of fences.
 /// A factory of fences.
 class FenceFactory : public MakeSingleton<FenceFactory>
 class FenceFactory : public MakeSingleton<FenceFactory>
 {
 {
-	friend class MicroFence;
 	friend class MicroFencePtrDeleter;
 	friend class MicroFencePtrDeleter;
 
 
 public:
 public:
-	/// Limit the alive fences to avoid having too many file descriptors used in Linux.
-	static constexpr U32 kMaxAliveFences = 32;
-
-	FenceFactory() = default;
-
-	~FenceFactory();
-
 	/// Create a new fence pointer.
 	/// Create a new fence pointer.
-	MicroFencePtr newInstance()
+	MicroFencePtr newInstance(CString name = "unnamed")
 	{
 	{
-		return MicroFencePtr(newFence());
+		return MicroFencePtr(m_factory.newFence(name));
 	}
 	}
 
 
-	void trimSignaledFences(Bool wait);
-
 private:
 private:
-	GrDynamicArray<MicroFence*> m_fences;
-	U32 m_aliveFenceCount = 0;
-	Mutex m_mtx;
+	MicroFenceFactory<MicroFenceImpl> m_factory;
 
 
-	MicroFence* newFence();
-	void deleteFence(MicroFence* fence);
+	void deleteFence(VulkanMicroFence* fence)
+	{
+		m_factory.releaseFence(fence);
+	}
 };
 };
+
+inline void MicroFencePtrDeleter::operator()(VulkanMicroFence* fence)
+{
+	ANKI_ASSERT(fence);
+	FenceFactory::getSingleton().deleteFence(fence);
+}
 /// @}
 /// @}
 
 
 } // end namespace anki
 } // end namespace anki

+ 25 - 139
AnKi/Gr/Vulkan/VkFrameGarbageCollector.cpp

@@ -6,171 +6,57 @@
 #include <AnKi/Gr/Vulkan/VkFrameGarbageCollector.h>
 #include <AnKi/Gr/Vulkan/VkFrameGarbageCollector.h>
 #include <AnKi/Gr/Vulkan/VkGrManager.h>
 #include <AnKi/Gr/Vulkan/VkGrManager.h>
 #include <AnKi/Gr/Vulkan/VkDescriptor.h>
 #include <AnKi/Gr/Vulkan/VkDescriptor.h>
-#include <AnKi/Gr/Fence.h>
 
 
 namespace anki {
 namespace anki {
 
 
-FrameGarbageCollector::~FrameGarbageCollector()
+TextureGarbage::~TextureGarbage()
 {
 {
-	destroy();
-}
+	const VkDevice dev = getVkDevice();
 
 
-void FrameGarbageCollector::collectGarbage()
-{
-	if(m_frames.isEmpty()) [[likely]]
+	for(VkImageView viewHandle : m_viewHandles)
 	{
 	{
-		return;
+		vkDestroyImageView(dev, viewHandle, nullptr);
 	}
 	}
 
 
-	const VkDevice dev = getVkDevice();
-
-	IntrusiveList<FrameGarbage> newFrames;
-	while(!m_frames.isEmpty())
+	for(U32 bindlessIndex : m_bindlessIndices)
 	{
 	{
-		FrameGarbage& frame = *m_frames.popFront();
-
-		if(frame.m_fence.isCreated() && !frame.m_fence->done())
-		{
-			ANKI_ASSERT(!frame.m_textureGarbage.isEmpty() || !frame.m_bufferGarbage.isEmpty());
-			newFrames.pushBack(&frame);
-			continue;
-		}
-
-		// Frame is done, dispose garbage and destroy it
-
-		// Dispose texture garbage
-		while(!frame.m_textureGarbage.isEmpty())
-		{
-			TextureGarbage* textureGarbage = frame.m_textureGarbage.popBack();
-
-			for(VkImageView viewHandle : textureGarbage->m_viewHandles)
-			{
-				vkDestroyImageView(dev, viewHandle, nullptr);
-			}
-
-			for(U32 bindlessIndex : textureGarbage->m_bindlessIndices)
-			{
-				BindlessDescriptorSet::getSingleton().unbindTexture(bindlessIndex);
-			}
-
-			if(textureGarbage->m_imageHandle)
-			{
-				vkDestroyImage(dev, textureGarbage->m_imageHandle, nullptr);
-			}
-
-			if(textureGarbage->m_memoryHandle)
-			{
-				GpuMemoryManager::getSingleton().freeMemory(textureGarbage->m_memoryHandle);
-			}
-
-			deleteInstance(GrMemoryPool::getSingleton(), textureGarbage);
-		}
-
-		// Dispose buffer garbage
-		while(!frame.m_bufferGarbage.isEmpty())
-		{
-			BufferGarbage* bufferGarbage = frame.m_bufferGarbage.popBack();
-
-			for(VkBufferView view : bufferGarbage->m_viewHandles)
-			{
-				vkDestroyBufferView(dev, view, nullptr);
-			}
-
-			if(bufferGarbage->m_bufferHandle)
-			{
-				vkDestroyBuffer(dev, bufferGarbage->m_bufferHandle, nullptr);
-			}
-
-			if(bufferGarbage->m_memoryHandle)
-			{
-				GpuMemoryManager::getSingleton().freeMemory(bufferGarbage->m_memoryHandle);
-			}
-
-			deleteInstance(GrMemoryPool::getSingleton(), bufferGarbage);
-		}
-
-		// Dispose AS garbage
-		while(!frame.m_asGarbage.isEmpty())
-		{
-			ASGarbage* garbage = frame.m_asGarbage.popBack();
-			vkDestroyAccelerationStructureKHR(getGrManagerImpl().getDevice(), garbage->m_asHandle, nullptr);
-			deleteInstance(GrMemoryPool::getSingleton(), garbage);
-		}
-
-		deleteInstance(GrMemoryPool::getSingleton(), &frame);
+		BindlessDescriptorSet::getSingleton().unbindTexture(bindlessIndex);
 	}
 	}
 
 
-	m_frames = std::move(newFrames);
-}
-
-FrameGarbageCollector::FrameGarbage& FrameGarbageCollector::getFrame()
-{
-	if(!m_frames.isEmpty() && !m_frames.getBack().m_fence.isCreated())
+	if(m_imageHandle)
 	{
 	{
-		// Do nothing
+		vkDestroyImage(dev, m_imageHandle, nullptr);
 	}
 	}
-	else
+
+	if(m_memoryHandle)
 	{
 	{
-		FrameGarbage* newGarbage = newInstance<FrameGarbage>(GrMemoryPool::getSingleton());
-		m_frames.pushBack(newGarbage);
+		GpuMemoryManager::getSingleton().freeMemory(m_memoryHandle);
 	}
 	}
-
-	return m_frames.getBack();
 }
 }
 
 
-void FrameGarbageCollector::setNewFrame(MicroFencePtr frameFence)
+BufferGarbage::~BufferGarbage()
 {
 {
-	ANKI_ASSERT(frameFence.isCreated());
-
-	LockGuard<Mutex> lock(m_mtx);
-	ANKI_ASSERT(m_initialized);
+	const VkDevice dev = getVkDevice();
 
 
-	if(!m_frames.isEmpty() && !m_frames.getBack().m_fence.isCreated())
+	for(VkBufferView view : m_viewHandles)
 	{
 	{
-		// Last frame is without a fence, asign the fence to not not have it garbage collected
-		m_frames.getBack().m_fence = std::move(frameFence);
+		vkDestroyBufferView(dev, view, nullptr);
 	}
 	}
 
 
-	collectGarbage();
-}
-
-void FrameGarbageCollector::newTextureGarbage(TextureGarbage* textureGarbage)
-{
-	ANKI_ASSERT(textureGarbage);
-	LockGuard<Mutex> lock(m_mtx);
-	ANKI_ASSERT(m_initialized);
-	FrameGarbage& frame = getFrame();
-	frame.m_textureGarbage.pushBack(textureGarbage);
-}
-
-void FrameGarbageCollector::newBufferGarbage(BufferGarbage* bufferGarbage)
-{
-	ANKI_ASSERT(bufferGarbage);
-	LockGuard<Mutex> lock(m_mtx);
-	ANKI_ASSERT(m_initialized);
-	FrameGarbage& frame = getFrame();
-	frame.m_bufferGarbage.pushBack(bufferGarbage);
-}
+	if(m_bufferHandle)
+	{
+		vkDestroyBuffer(dev, m_bufferHandle, nullptr);
+	}
 
 
-void FrameGarbageCollector::newASGarbage(ASGarbage* garbage)
-{
-	ANKI_ASSERT(garbage);
-	LockGuard<Mutex> lock(m_mtx);
-	ANKI_ASSERT(m_initialized);
-	FrameGarbage& frame = getFrame();
-	frame.m_asGarbage.pushBack(garbage);
+	if(m_memoryHandle)
+	{
+		GpuMemoryManager::getSingleton().freeMemory(m_memoryHandle);
+	}
 }
 }
 
 
-void FrameGarbageCollector::destroy()
+ASGarbage::~ASGarbage()
 {
 {
-	LockGuard<Mutex> lock(m_mtx);
-
-	collectGarbage();
-	ANKI_ASSERT(m_frames.isEmpty());
-
-#if ANKI_EXTRA_CHECKS
-	m_initialized = false;
-#endif
+	vkDestroyAccelerationStructureKHR(getGrManagerImpl().getDevice(), m_asHandle, nullptr);
 }
 }
 
 
 } // end namespace anki
 } // end namespace anki

+ 10 - 53
AnKi/Gr/Vulkan/VkFrameGarbageCollector.h

@@ -7,9 +7,7 @@
 
 
 #include <AnKi/Gr/Vulkan/VkCommon.h>
 #include <AnKi/Gr/Vulkan/VkCommon.h>
 #include <AnKi/Gr/Vulkan/VkGpuMemoryManager.h>
 #include <AnKi/Gr/Vulkan/VkGpuMemoryManager.h>
-#include <AnKi/Gr/Vulkan/VkFenceFactory.h>
-#include <AnKi/Util/DynamicArray.h>
-#include <AnKi/Util/List.h>
+#include <AnKi/Gr/BackendCommon/FrameGarbageCollector.h>
 
 
 namespace anki {
 namespace anki {
 
 
@@ -24,6 +22,8 @@ public:
 	GrDynamicArray<U32> m_bindlessIndices;
 	GrDynamicArray<U32> m_bindlessIndices;
 	VkImage m_imageHandle = VK_NULL_HANDLE;
 	VkImage m_imageHandle = VK_NULL_HANDLE;
 	GpuMemoryHandle m_memoryHandle;
 	GpuMemoryHandle m_memoryHandle;
+
+	~TextureGarbage();
 };
 };
 
 
 /// @memberof FrameGarbageCollector
 /// @memberof FrameGarbageCollector
@@ -33,6 +33,8 @@ public:
 	VkBuffer m_bufferHandle = VK_NULL_HANDLE;
 	VkBuffer m_bufferHandle = VK_NULL_HANDLE;
 	GrDynamicArray<VkBufferView> m_viewHandles;
 	GrDynamicArray<VkBufferView> m_viewHandles;
 	GpuMemoryHandle m_memoryHandle;
 	GpuMemoryHandle m_memoryHandle;
+
+	~BufferGarbage();
 };
 };
 
 
 /// AS have more data (buffers) that build them but don't bother storing them since buffers will be automatically garbage collected as well.
 /// AS have more data (buffers) that build them but don't bother storing them since buffers will be automatically garbage collected as well.
@@ -41,59 +43,14 @@ class ASGarbage : public IntrusiveListEnabled<ASGarbage>
 {
 {
 public:
 public:
 	VkAccelerationStructureKHR m_asHandle = VK_NULL_HANDLE;
 	VkAccelerationStructureKHR m_asHandle = VK_NULL_HANDLE;
+
+	~ASGarbage();
 };
 };
 
 
-/// This class gathers various garbages and disposes them when in some later frame where it is safe to do so. This is used on bindless textures and
-/// buffers where we have to wait until the frame where they were deleted is done.
-class FrameGarbageCollector
+class VulkanFrameGarbageCollector :
+	public FrameGarbageCollector<TextureGarbage, BufferGarbage, ASGarbage>,
+	public MakeSingleton<VulkanFrameGarbageCollector>
 {
 {
-public:
-	FrameGarbageCollector() = default;
-
-	~FrameGarbageCollector();
-
-	void init()
-	{
-#if ANKI_EXTRA_CHECKS
-		m_initialized = true;
-#endif
-	}
-
-	void destroy();
-
-	/// Sets a new frame and collects garbage as well.
-	/// @note It's thread-safe.
-	void setNewFrame(MicroFencePtr frameFence);
-
-	/// @note It's thread-safe.
-	void newTextureGarbage(TextureGarbage* textureGarbage);
-
-	/// @note It's thread-safe.
-	void newBufferGarbage(BufferGarbage* bufferGarbage);
-
-	/// @note It's thread-safe.
-	void newASGarbage(ASGarbage* garbage);
-
-private:
-	class FrameGarbage : public IntrusiveListEnabled<FrameGarbage>
-	{
-	public:
-		IntrusiveList<TextureGarbage> m_textureGarbage;
-		IntrusiveList<BufferGarbage> m_bufferGarbage;
-		IntrusiveList<ASGarbage> m_asGarbage;
-		MicroFencePtr m_fence;
-	};
-
-	Mutex m_mtx;
-	IntrusiveList<FrameGarbage> m_frames;
-
-#if ANKI_EXTRA_CHECKS
-	Bool m_initialized = false;
-#endif
-
-	void collectGarbage();
-
-	FrameGarbage& getFrame();
 };
 };
 /// @}
 /// @}
 
 

+ 200 - 176
AnKi/Gr/Vulkan/VkGrManager.cpp

@@ -22,6 +22,7 @@
 #include <AnKi/Gr/Vulkan/VkFence.h>
 #include <AnKi/Gr/Vulkan/VkFence.h>
 #include <AnKi/Gr/Vulkan/VkGpuMemoryManager.h>
 #include <AnKi/Gr/Vulkan/VkGpuMemoryManager.h>
 #include <AnKi/Gr/Vulkan/VkDescriptor.h>
 #include <AnKi/Gr/Vulkan/VkDescriptor.h>
+#include <AnKi/Gr/Vulkan/VkFrameGarbageCollector.h>
 
 
 #include <AnKi/Window/NativeWindow.h>
 #include <AnKi/Window/NativeWindow.h>
 #if ANKI_WINDOWING_SYSTEM_SDL
 #if ANKI_WINDOWING_SYSTEM_SDL
@@ -81,16 +82,22 @@ Error GrManager::init(GrManagerInitInfo& inf)
 	return self.init(inf);
 	return self.init(inf);
 }
 }
 
 
+void GrManager::beginFrame()
+{
+	ANKI_VK_SELF(GrManagerImpl);
+	self.beginFrameInternal();
+}
+
 TexturePtr GrManager::acquireNextPresentableTexture()
 TexturePtr GrManager::acquireNextPresentableTexture()
 {
 {
 	ANKI_VK_SELF(GrManagerImpl);
 	ANKI_VK_SELF(GrManagerImpl);
 	return self.acquireNextPresentableTexture();
 	return self.acquireNextPresentableTexture();
 }
 }
 
 
-void GrManager::swapBuffers()
+void GrManager::endFrame()
 {
 {
 	ANKI_VK_SELF(GrManagerImpl);
 	ANKI_VK_SELF(GrManagerImpl);
-	self.endFrame();
+	self.endFrameInternal();
 }
 }
 
 
 void GrManager::finish()
 void GrManager::finish()
@@ -140,154 +147,25 @@ ANKI_NEW_GR_OBJECT(GrUpscaler)
 void GrManager::submit(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFences, FencePtr* signalFence)
 void GrManager::submit(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFences, FencePtr* signalFence)
 {
 {
 	ANKI_VK_SELF(GrManagerImpl);
 	ANKI_VK_SELF(GrManagerImpl);
-
-	// First thing, create a fence
-	MicroFencePtr fence = FenceFactory::getSingleton().newInstance();
-
-	// Gather command buffers
-	GrDynamicArray<VkCommandBuffer> vkCmdbs;
-	vkCmdbs.resizeStorage(cmdbs.getSize());
-	Bool renderedToDefaultFb = false;
-	GpuQueueType queueType = GpuQueueType::kCount;
-	for(U32 i = 0; i < cmdbs.getSize(); ++i)
-	{
-		CommandBufferImpl& cmdb = *static_cast<CommandBufferImpl*>(cmdbs[i]);
-		ANKI_ASSERT(cmdb.isFinalized());
-		renderedToDefaultFb = renderedToDefaultFb || cmdb.renderedToDefaultFramebuffer();
-#if ANKI_ASSERTIONS_ENABLED
-		cmdb.setSubmitted();
-#endif
-
-		MicroCommandBuffer& mcmdb = *cmdb.getMicroCommandBuffer();
-		mcmdb.setFence(fence.get());
-
-		if(i == 0)
-		{
-			queueType = mcmdb.getVulkanQueueType();
-		}
-		else
-		{
-			ANKI_ASSERT(queueType == mcmdb.getVulkanQueueType() && "All cmdbs should be for the same queue");
-		}
-
-		vkCmdbs.emplaceBack(cmdb.getHandle());
-	}
-
-	// Gather wait semaphores
-	GrDynamicArray<VkSemaphore> waitSemaphores;
-	GrDynamicArray<VkPipelineStageFlags> waitStages;
-	GrDynamicArray<U64> waitTimelineValues;
-	waitSemaphores.resizeStorage(waitFences.getSize());
-	waitStages.resizeStorage(waitFences.getSize());
-	waitTimelineValues.resizeStorage(waitFences.getSize());
-	for(U32 i = 0; i < waitFences.getSize(); ++i)
-	{
-		FenceImpl& impl = static_cast<FenceImpl&>(*waitFences[i]);
-		MicroSemaphore& msem = *impl.m_semaphore;
-		ANKI_ASSERT(msem.isTimeline());
-
-		waitSemaphores.emplaceBack(msem.getHandle());
-		waitStages.emplaceBack(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
-		waitTimelineValues.emplaceBack(msem.getSemaphoreValue());
-
-		// Refresh the fence because the semaphore can't be recycled until the current submission is done
-		msem.setFence(fence.get());
-	}
-
-	// Signal semaphore
-	GrDynamicArray<VkSemaphore> signalSemaphores;
-	GrDynamicArray<U64> signalTimelineValues;
-	if(signalFence)
-	{
-		FenceImpl* fenceImpl = anki::newInstance<FenceImpl>(GrMemoryPool::getSingleton(), "SignalFence");
-		fenceImpl->m_semaphore = SemaphoreFactory::getSingleton().newInstance(fence, true);
-
-		signalFence->reset(fenceImpl);
-
-		signalSemaphores.emplaceBack(fenceImpl->m_semaphore->getHandle());
-		signalTimelineValues.emplaceBack(fenceImpl->m_semaphore->getNextSemaphoreValue());
-	}
-
-	// Submit
-	{
-		// Protect the class, the queue and other stuff
-		LockGuard<Mutex> lock(self.m_globalMtx);
-
-		// Do some special stuff for the last command buffer
-		GrManagerImpl::PerFrame& frame = self.m_perFrame[self.m_frame % kMaxFramesInFlight];
-		if(renderedToDefaultFb)
-		{
-			// Wait semaphore
-			waitSemaphores.emplaceBack(frame.m_acquireSemaphore->getHandle());
-
-			// That depends on how we use the swapchain img. Be a bit conservative
-			waitStages.emplaceBack(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
-
-			// Set something
-			waitTimelineValues.emplaceBack(0);
-
-			// Refresh the fence because the semaphore can't be recycled until the current submission is done
-			frame.m_acquireSemaphore->setFence(fence.get());
-
-			// Create the semaphore to signal and then wait on present
-			ANKI_ASSERT(!frame.m_renderSemaphore && "Only one begin/end render pass is allowed with the default fb");
-			frame.m_renderSemaphore = SemaphoreFactory::getSingleton().newInstance(fence, false);
-
-			signalSemaphores.emplaceBack(frame.m_renderSemaphore->getHandle());
-
-			// Increment the timeline values as well because the spec wants a dummy value even for non-timeline semaphores
-			signalTimelineValues.emplaceBack(0);
-
-			// Update the frame fence
-			frame.m_presentFence = fence;
-
-			// Update the swapchain's fence
-			self.m_crntSwapchain->setFence(fence.get());
-
-			frame.m_queueWroteToSwapchainImage = queueType;
-		}
-
-		// Submit
-		VkSubmitInfo submit = {};
-		submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
-		submit.waitSemaphoreCount = waitSemaphores.getSize();
-		submit.pWaitSemaphores = (waitSemaphores.getSize()) ? waitSemaphores.getBegin() : nullptr;
-		submit.signalSemaphoreCount = signalSemaphores.getSize();
-		submit.pSignalSemaphores = (signalSemaphores.getSize()) ? signalSemaphores.getBegin() : nullptr;
-		submit.pWaitDstStageMask = (waitStages.getSize()) ? waitStages.getBegin() : nullptr;
-		submit.commandBufferCount = vkCmdbs.getSize();
-		submit.pCommandBuffers = (vkCmdbs.getSize()) ? vkCmdbs.getBegin() : nullptr;
-
-		VkTimelineSemaphoreSubmitInfo timelineInfo = {};
-		timelineInfo.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO;
-		timelineInfo.waitSemaphoreValueCount = waitSemaphores.getSize();
-		timelineInfo.pWaitSemaphoreValues = (waitSemaphores.getSize()) ? waitTimelineValues.getBegin() : nullptr;
-		timelineInfo.signalSemaphoreValueCount = signalTimelineValues.getSize();
-		timelineInfo.pSignalSemaphoreValues = (signalTimelineValues.getSize()) ? signalTimelineValues.getBegin() : nullptr;
-		appendPNextList(submit, &timelineInfo);
-
-		ANKI_TRACE_SCOPED_EVENT(VkQueueSubmit);
-		ANKI_VK_CHECKF(vkQueueSubmit(self.m_queues[queueType], 1, &submit, fence->getHandle()));
-	}
-
-	// Garbage work
-	if(renderedToDefaultFb)
-	{
-		self.m_frameGarbageCollector.setNewFrame(fence);
-	}
+	self.submitInternal(cmdbs, waitFences, signalFence);
 }
 }
 
 
 GrManagerImpl::~GrManagerImpl()
 GrManagerImpl::~GrManagerImpl()
 {
 {
 	ANKI_VK_LOGI("Destroying Vulkan backend");
 	ANKI_VK_LOGI("Destroying Vulkan backend");
 
 
-	// 1st THING: wait for the present fences because I don't know if waiting on queue will cover this
+	// 1st THING: wait for all fences
 	for(PerFrame& frame : m_perFrame)
 	for(PerFrame& frame : m_perFrame)
 	{
 	{
-		if(frame.m_presentFence.isCreated())
+		for(MicroFencePtr& fence : frame.m_fences)
 		{
 		{
-			frame.m_presentFence->wait();
+			if(fence)
+			{
+				fence->clientWait(kMaxSecond);
+			}
 		}
 		}
+
+		frame.m_fences.destroy();
 	}
 	}
 
 
 	// 2nd THING: wait for the GPU
 	// 2nd THING: wait for the GPU
@@ -306,7 +184,6 @@ GrManagerImpl::~GrManagerImpl()
 
 
 	for(PerFrame& frame : m_perFrame)
 	for(PerFrame& frame : m_perFrame)
 	{
 	{
-		frame.m_presentFence.reset(nullptr);
 		frame.m_acquireSemaphore.reset(nullptr);
 		frame.m_acquireSemaphore.reset(nullptr);
 		frame.m_renderSemaphore.reset(nullptr);
 		frame.m_renderSemaphore.reset(nullptr);
 	}
 	}
@@ -321,7 +198,7 @@ GrManagerImpl::~GrManagerImpl()
 	SemaphoreFactory::freeSingleton(); // Destroy before fences
 	SemaphoreFactory::freeSingleton(); // Destroy before fences
 	SamplerFactory::freeSingleton();
 	SamplerFactory::freeSingleton();
 	SwapchainFactory::freeSingleton(); // Destroy before fences
 	SwapchainFactory::freeSingleton(); // Destroy before fences
-	m_frameGarbageCollector.destroy();
+	VulkanFrameGarbageCollector::freeSingleton();
 
 
 	GpuMemoryManager::freeSingleton();
 	GpuMemoryManager::freeSingleton();
 	PipelineLayoutFactory2::freeSingleton();
 	PipelineLayoutFactory2::freeSingleton();
@@ -399,11 +276,7 @@ Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
 	TimestampQueryFactory::allocateSingleton();
 	TimestampQueryFactory::allocateSingleton();
 	PrimitivesPassedClippingFactory::allocateSingleton();
 	PrimitivesPassedClippingFactory::allocateSingleton();
 	SamplerFactory::allocateSingleton();
 	SamplerFactory::allocateSingleton();
-
-	for(PerFrame& f : m_perFrame)
-	{
-		resetFrame(f);
-	}
+	VulkanFrameGarbageCollector::allocateSingleton();
 
 
 	// See if unaligned formats are supported
 	// See if unaligned formats are supported
 	{
 	{
@@ -435,8 +308,6 @@ Error GrManagerImpl::initInternal(const GrManagerInitInfo& init)
 
 
 	PipelineLayoutFactory2::allocateSingleton();
 	PipelineLayoutFactory2::allocateSingleton();
 
 
-	m_frameGarbageCollector.init();
-
 	return Error::kNone;
 	return Error::kNone;
 }
 }
 
 
@@ -1361,23 +1232,64 @@ void GrManagerImpl::freeCallback(void* userData, void* ptr)
 }
 }
 #endif
 #endif
 
 
+void GrManagerImpl::beginFrameInternal()
+{
+	ANKI_TRACE_FUNCTION();
+
+	LockGuard<Mutex> lock(m_globalMtx);
+
+	// Do that at begining frame, ALWAYS
+	++m_frame;
+
+	// Wait for the oldest frame because we don't want to start the new one too early
+	PerFrame& frame = m_perFrame[m_frame % m_perFrame.getSize()];
+
+	for(MicroFencePtr& fence : frame.m_fences)
+	{
+		if(fence)
+		{
+			const Bool signaled = fence->clientWait(kMaxSecond);
+			if(!signaled)
+			{
+				ANKI_VK_LOGF("Timeout detected");
+			}
+		}
+	}
+
+	frame.m_fences.destroy();
+
+	// Reset the rest of the frame (after the fences)
+	frame.m_acquireSemaphore.reset(nullptr);
+	frame.m_renderSemaphore.reset(nullptr);
+	frame.m_queueWroteToSwapchainImage = GpuQueueType::kCount;
+
+	// Clear garbage
+	VulkanFrameGarbageCollector::getSingleton().beginFrameAndCollectGarbage();
+}
+
 TexturePtr GrManagerImpl::acquireNextPresentableTexture()
 TexturePtr GrManagerImpl::acquireNextPresentableTexture()
 {
 {
-	ANKI_TRACE_SCOPED_EVENT(VkAcquireImage);
+	ANKI_TRACE_FUNCTION();
+
+	// Create some objets outside the lock
+	MicroFencePtr fence = FenceFactory::getSingleton().newInstance("Acquire");
+	MicroSemaphorePtr acquireSemaphore = SemaphoreFactory::getSingleton().newInstance(fence, false, "Acquire");
 
 
 	LockGuard<Mutex> lock(m_globalMtx);
 	LockGuard<Mutex> lock(m_globalMtx);
 
 
-	PerFrame& frame = m_perFrame[m_frame % kMaxFramesInFlight];
+	PerFrame& frame = m_perFrame[m_frame % m_perFrame.getSize()];
+
+	ANKI_ASSERT(!frame.m_acquireSemaphore.isCreated() && "Forgot to begin the frame");
 
 
 	// Create sync objects
 	// Create sync objects
-	MicroFencePtr fence = FenceFactory::getSingleton().newInstance();
-	frame.m_acquireSemaphore = SemaphoreFactory::getSingleton().newInstance(fence, false);
+	frame.m_fences.emplaceBack(fence);
+	frame.m_acquireSemaphore = acquireSemaphore;
 
 
 	// Get new image
 	// Get new image
 	uint32_t imageIdx;
 	uint32_t imageIdx;
 
 
 	VkResult res = vkAcquireNextImageKHR(m_device, m_crntSwapchain->m_swapchain, UINT64_MAX, frame.m_acquireSemaphore->getHandle(),
 	VkResult res = vkAcquireNextImageKHR(m_device, m_crntSwapchain->m_swapchain, UINT64_MAX, frame.m_acquireSemaphore->getHandle(),
-										 fence->getHandle(), &imageIdx);
+										 fence->getImplementation().m_handle, &imageIdx);
 
 
 	if(res == VK_ERROR_OUT_OF_DATE_KHR)
 	if(res == VK_ERROR_OUT_OF_DATE_KHR)
 	{
 	{
@@ -1394,7 +1306,7 @@ TexturePtr GrManagerImpl::acquireNextPresentableTexture()
 
 
 		// Can't fail a second time
 		// Can't fail a second time
 		ANKI_VK_CHECKF(vkAcquireNextImageKHR(m_device, m_crntSwapchain->m_swapchain, UINT64_MAX, frame.m_acquireSemaphore->getHandle(),
 		ANKI_VK_CHECKF(vkAcquireNextImageKHR(m_device, m_crntSwapchain->m_swapchain, UINT64_MAX, frame.m_acquireSemaphore->getHandle(),
-											 fence->getHandle(), &imageIdx));
+											 fence->getImplementation().m_handle, &imageIdx));
 	}
 	}
 	else
 	else
 	{
 	{
@@ -1405,27 +1317,17 @@ TexturePtr GrManagerImpl::acquireNextPresentableTexture()
 	return m_crntSwapchain->m_textures[imageIdx];
 	return m_crntSwapchain->m_textures[imageIdx];
 }
 }
 
 
-void GrManagerImpl::endFrame()
+void GrManagerImpl::endFrameInternal()
 {
 {
-	ANKI_TRACE_SCOPED_EVENT(VkPresent);
+	ANKI_TRACE_FUNCTION();
 
 
 	LockGuard<Mutex> lock(m_globalMtx);
 	LockGuard<Mutex> lock(m_globalMtx);
 
 
-	PerFrame& frame = m_perFrame[m_frame % kMaxFramesInFlight];
-
-	// Wait for the fence of N-2 frame
-	const U waitFrameIdx = (m_frame + 1) % kMaxFramesInFlight;
-	PerFrame& waitFrame = m_perFrame[waitFrameIdx];
-	if(waitFrame.m_presentFence)
-	{
-		waitFrame.m_presentFence->wait();
-	}
-
-	resetFrame(waitFrame);
+	PerFrame& frame = m_perFrame[m_frame % m_perFrame.getSize()];
 
 
 	if(!frame.m_renderSemaphore)
 	if(!frame.m_renderSemaphore)
 	{
 	{
-		ANKI_VK_LOGW("Nobody draw to the default framebuffer");
+		ANKI_VK_LOGW("Nobody draw to the swapchain");
 	}
 	}
 
 
 	// Present
 	// Present
@@ -1462,16 +1364,138 @@ void GrManagerImpl::endFrame()
 	}
 	}
 
 
 	GpuMemoryManager::getSingleton().updateStats();
 	GpuMemoryManager::getSingleton().updateStats();
-
-	// Finalize
-	++m_frame;
 }
 }
 
 
-void GrManagerImpl::resetFrame(PerFrame& frame)
+void GrManagerImpl::submitInternal(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFences, FencePtr* signalFence)
 {
 {
-	frame.m_presentFence.reset(nullptr);
-	frame.m_acquireSemaphore.reset(nullptr);
-	frame.m_renderSemaphore.reset(nullptr);
+	// First thing, create a fence
+	MicroFencePtr fence = FenceFactory::getSingleton().newInstance("Submit");
+
+	// Gather command buffers
+	GrDynamicArray<VkCommandBuffer> vkCmdbs;
+	vkCmdbs.resizeStorage(cmdbs.getSize());
+	Bool renderedToDefaultFb = false;
+	GpuQueueType queueType = GpuQueueType::kCount;
+	for(U32 i = 0; i < cmdbs.getSize(); ++i)
+	{
+		CommandBufferImpl& cmdb = *static_cast<CommandBufferImpl*>(cmdbs[i]);
+		ANKI_ASSERT(cmdb.isFinalized());
+		renderedToDefaultFb = renderedToDefaultFb || cmdb.renderedToDefaultFramebuffer();
+#if ANKI_ASSERTIONS_ENABLED
+		cmdb.setSubmitted();
+#endif
+
+		MicroCommandBuffer& mcmdb = *cmdb.getMicroCommandBuffer();
+		mcmdb.setFence(fence.get());
+
+		if(i == 0)
+		{
+			queueType = mcmdb.getVulkanQueueType();
+		}
+		else
+		{
+			ANKI_ASSERT(queueType == mcmdb.getVulkanQueueType() && "All cmdbs should be for the same queue");
+		}
+
+		vkCmdbs.emplaceBack(cmdb.getHandle());
+	}
+
+	// Gather wait semaphores
+	GrDynamicArray<VkSemaphore> waitSemaphores;
+	GrDynamicArray<VkPipelineStageFlags> waitStages;
+	GrDynamicArray<U64> waitTimelineValues;
+	waitSemaphores.resizeStorage(waitFences.getSize());
+	waitStages.resizeStorage(waitFences.getSize());
+	waitTimelineValues.resizeStorage(waitFences.getSize());
+	for(U32 i = 0; i < waitFences.getSize(); ++i)
+	{
+		FenceImpl& impl = static_cast<FenceImpl&>(*waitFences[i]);
+		MicroSemaphore& msem = *impl.m_semaphore;
+		ANKI_ASSERT(msem.isTimeline());
+
+		waitSemaphores.emplaceBack(msem.getHandle());
+		waitStages.emplaceBack(VK_PIPELINE_STAGE_ALL_COMMANDS_BIT);
+		waitTimelineValues.emplaceBack(msem.getSemaphoreValue());
+
+		// Refresh the fence because the semaphore can't be recycled until the current submission is done
+		msem.setFence(fence.get());
+	}
+
+	// Signal semaphore
+	GrDynamicArray<VkSemaphore> signalSemaphores;
+	GrDynamicArray<U64> signalTimelineValues;
+	if(signalFence)
+	{
+		FenceImpl* fenceImpl = anki::newInstance<FenceImpl>(GrMemoryPool::getSingleton(), "SignalFence");
+		fenceImpl->m_semaphore = SemaphoreFactory::getSingleton().newInstance(fence, true, "SubmitSignal");
+
+		signalFence->reset(fenceImpl);
+
+		signalSemaphores.emplaceBack(fenceImpl->m_semaphore->getHandle());
+		signalTimelineValues.emplaceBack(fenceImpl->m_semaphore->getNextSemaphoreValue());
+	}
+
+	// Submit
+	{
+		// Protect the class, the queue and other stuff
+		LockGuard<Mutex> lock(m_globalMtx);
+
+		// Do some special stuff for the last command buffer
+		GrManagerImpl::PerFrame& frame = m_perFrame[m_frame % m_perFrame.getSize()];
+		if(renderedToDefaultFb)
+		{
+			// Wait semaphore
+			waitSemaphores.emplaceBack(frame.m_acquireSemaphore->getHandle());
+
+			// That depends on how we use the swapchain img. Be a bit conservative
+			waitStages.emplaceBack(VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT | VK_PIPELINE_STAGE_COMPUTE_SHADER_BIT);
+
+			// Set something
+			waitTimelineValues.emplaceBack(0);
+
+			// Refresh the fence because the semaphore can't be recycled until the current submission is done
+			frame.m_acquireSemaphore->setFence(fence.get());
+
+			// Create the semaphore to signal and then wait on present
+			ANKI_ASSERT(!frame.m_renderSemaphore && "Only one begin/end render pass is allowed with the default fb");
+			frame.m_renderSemaphore = SemaphoreFactory::getSingleton().newInstance(fence, false, "RenderToSwapchain");
+			frame.m_renderSemaphore->setFence(fence.get());
+
+			signalSemaphores.emplaceBack(frame.m_renderSemaphore->getHandle());
+
+			// Increment the timeline values as well because the spec wants a dummy value even for non-timeline semaphores
+			signalTimelineValues.emplaceBack(0);
+
+			// Update the swapchain's fence
+			m_crntSwapchain->setFence(fence.get());
+
+			frame.m_queueWroteToSwapchainImage = queueType;
+		}
+
+		frame.m_fences.emplaceBack(fence);
+
+		// Submit
+		VkSubmitInfo submit = {};
+		submit.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+		submit.waitSemaphoreCount = waitSemaphores.getSize();
+		submit.pWaitSemaphores = (waitSemaphores.getSize()) ? waitSemaphores.getBegin() : nullptr;
+		submit.signalSemaphoreCount = signalSemaphores.getSize();
+		submit.pSignalSemaphores = (signalSemaphores.getSize()) ? signalSemaphores.getBegin() : nullptr;
+		submit.pWaitDstStageMask = (waitStages.getSize()) ? waitStages.getBegin() : nullptr;
+		submit.commandBufferCount = vkCmdbs.getSize();
+		submit.pCommandBuffers = (vkCmdbs.getSize()) ? vkCmdbs.getBegin() : nullptr;
+
+		VkTimelineSemaphoreSubmitInfo timelineInfo = {};
+		timelineInfo.sType = VK_STRUCTURE_TYPE_TIMELINE_SEMAPHORE_SUBMIT_INFO;
+		timelineInfo.waitSemaphoreValueCount = waitSemaphores.getSize();
+		timelineInfo.pWaitSemaphoreValues = (waitSemaphores.getSize()) ? waitTimelineValues.getBegin() : nullptr;
+		timelineInfo.signalSemaphoreValueCount = signalTimelineValues.getSize();
+		timelineInfo.pSignalSemaphoreValues = (signalTimelineValues.getSize()) ? signalTimelineValues.getBegin() : nullptr;
+		appendPNextList(submit, &timelineInfo);
+
+		ANKI_TRACE_SCOPED_EVENT(VkQueueSubmit);
+		ANKI_VK_CHECKF(vkQueueSubmit(m_queues[queueType], 1, &submit, fence->getImplementation().m_handle));
+	}
 }
 }
 
 
 void GrManagerImpl::finish()
 void GrManagerImpl::finish()

+ 6 - 12
AnKi/Gr/Vulkan/VkGrManager.h

@@ -10,7 +10,6 @@
 #include <AnKi/Gr/Vulkan/VkSemaphoreFactory.h>
 #include <AnKi/Gr/Vulkan/VkSemaphoreFactory.h>
 #include <AnKi/Gr/Vulkan/VkFenceFactory.h>
 #include <AnKi/Gr/Vulkan/VkFenceFactory.h>
 #include <AnKi/Gr/Vulkan/VkSwapchainFactory.h>
 #include <AnKi/Gr/Vulkan/VkSwapchainFactory.h>
-#include <AnKi/Gr/Vulkan/VkFrameGarbageCollector.h>
 #include <AnKi/Util/File.h>
 #include <AnKi/Util/File.h>
 
 
 namespace anki {
 namespace anki {
@@ -80,7 +79,11 @@ public:
 
 
 	TexturePtr acquireNextPresentableTexture();
 	TexturePtr acquireNextPresentableTexture();
 
 
-	void endFrame();
+	void beginFrameInternal();
+
+	void endFrameInternal();
+
+	void submitInternal(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFences, FencePtr* signalFence);
 
 
 	void finish();
 	void finish();
 
 
@@ -143,11 +146,6 @@ public:
 	/// @note It's thread-safe.
 	/// @note It's thread-safe.
 	void printPipelineShaderInfo(VkPipeline ppline, CString name, U64 hash = 0) const;
 	void printPipelineShaderInfo(VkPipeline ppline, CString name, U64 hash = 0) const;
 
 
-	FrameGarbageCollector& getFrameGarbageCollector()
-	{
-		return m_frameGarbageCollector;
-	}
-
 private:
 private:
 	U64 m_frame = 0;
 	U64 m_frame = 0;
 
 
@@ -183,7 +181,7 @@ private:
 	class PerFrame
 	class PerFrame
 	{
 	{
 	public:
 	public:
-		MicroFencePtr m_presentFence;
+		GrDynamicArray<MicroFencePtr> m_fences;
 		MicroSemaphorePtr m_acquireSemaphore;
 		MicroSemaphorePtr m_acquireSemaphore;
 
 
 		/// Signaled by the submit that renders to the default FB. Present waits for it.
 		/// Signaled by the submit that renders to the default FB. Present waits for it.
@@ -203,8 +201,6 @@ private:
 
 
 	VkPhysicalDeviceMemoryProperties m_memoryProperties;
 	VkPhysicalDeviceMemoryProperties m_memoryProperties;
 
 
-	FrameGarbageCollector m_frameGarbageCollector;
-
 	Error initInternal(const GrManagerInitInfo& init);
 	Error initInternal(const GrManagerInitInfo& init);
 	Error initInstance();
 	Error initInstance();
 	Error initSurface();
 	Error initSurface();
@@ -219,8 +215,6 @@ private:
 	static void freeCallback(void* userData, void* ptr);
 	static void freeCallback(void* userData, void* ptr);
 #endif
 #endif
 
 
-	void resetFrame(PerFrame& frame);
-
 	static VkBool32 debugReportCallbackEXT(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes,
 	static VkBool32 debugReportCallbackEXT(VkDebugUtilsMessageSeverityFlagBitsEXT messageSeverity, VkDebugUtilsMessageTypeFlagsEXT messageTypes,
 										   const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData);
 										   const VkDebugUtilsMessengerCallbackDataEXT* pCallbackData, void* pUserData);
 
 

+ 4 - 1
AnKi/Gr/Vulkan/VkSemaphoreFactory.cpp

@@ -4,6 +4,7 @@
 // http://www.anki3d.org/LICENSE
 // http://www.anki3d.org/LICENSE
 
 
 #include <AnKi/Gr/Vulkan/VkSemaphoreFactory.h>
 #include <AnKi/Gr/Vulkan/VkSemaphoreFactory.h>
+#include <AnKi/Gr/Vulkan/VkGrManager.h>
 #include <AnKi/Util/Tracer.h>
 #include <AnKi/Util/Tracer.h>
 
 
 namespace anki {
 namespace anki {
@@ -70,7 +71,7 @@ void MicroSemaphorePtrDeleter::operator()(MicroSemaphore* s)
 	}
 	}
 }
 }
 
 
-MicroSemaphorePtr SemaphoreFactory::newInstance(MicroFencePtr fence, Bool isTimeline)
+MicroSemaphorePtr SemaphoreFactory::newInstance(MicroFencePtr fence, Bool isTimeline, CString name)
 {
 {
 	ANKI_ASSERT(fence);
 	ANKI_ASSERT(fence);
 
 
@@ -91,6 +92,8 @@ MicroSemaphorePtr SemaphoreFactory::newInstance(MicroFencePtr fence, Bool isTime
 		}
 		}
 	}
 	}
 
 
+	getGrManagerImpl().trySetVulkanHandleName(name, VK_OBJECT_TYPE_SEMAPHORE, out->m_handle);
+
 	ANKI_ASSERT(out->m_refcount.getNonAtomically() == 0);
 	ANKI_ASSERT(out->m_refcount.getNonAtomically() == 0);
 	return MicroSemaphorePtr(out);
 	return MicroSemaphorePtr(out);
 }
 }

+ 3 - 3
AnKi/Gr/Vulkan/VkSemaphoreFactory.h

@@ -46,7 +46,7 @@ public:
 		return m_refcount.load();
 		return m_refcount.load();
 	}
 	}
 
 
-	MicroFence* getFence() const
+	VulkanMicroFence* getFence() const
 	{
 	{
 		return m_fence.tryGet();
 		return m_fence.tryGet();
 	}
 	}
@@ -57,7 +57,7 @@ public:
 		// Do nothing
 		// Do nothing
 	}
 	}
 
 
-	void setFence(MicroFence* fence)
+	void setFence(VulkanMicroFence* fence)
 	{
 	{
 		m_fence.reset(fence);
 		m_fence.reset(fence);
 	}
 	}
@@ -123,7 +123,7 @@ public:
 		m_timelineRecycler.destroy();
 		m_timelineRecycler.destroy();
 	}
 	}
 
 
-	MicroSemaphorePtr newInstance(MicroFencePtr fence, Bool isTimeline);
+	MicroSemaphorePtr newInstance(MicroFencePtr fence, Bool isTimeline, CString name);
 
 
 private:
 private:
 	MicroObjectRecycler<MicroSemaphore> m_binaryRecycler;
 	MicroObjectRecycler<MicroSemaphore> m_binaryRecycler;

+ 2 - 2
AnKi/Gr/Vulkan/VkSwapchainFactory.h

@@ -44,12 +44,12 @@ public:
 		return m_refcount.load();
 		return m_refcount.load();
 	}
 	}
 
 
-	void setFence(MicroFence* fence)
+	void setFence(VulkanMicroFence* fence)
 	{
 	{
 		m_fence.reset(fence);
 		m_fence.reset(fence);
 	}
 	}
 
 
-	MicroFence* getFence() const
+	VulkanMicroFence* getFence() const
 	{
 	{
 		return m_fence.tryGet();
 		return m_fence.tryGet();
 	}
 	}

+ 2 - 1
AnKi/Gr/Vulkan/VkTexture.cpp

@@ -6,6 +6,7 @@
 #include <AnKi/Gr/Vulkan/VkTexture.h>
 #include <AnKi/Gr/Vulkan/VkTexture.h>
 #include <AnKi/Gr/Vulkan/VkGrManager.h>
 #include <AnKi/Gr/Vulkan/VkGrManager.h>
 #include <AnKi/Gr/Vulkan/VkDescriptor.h>
 #include <AnKi/Gr/Vulkan/VkDescriptor.h>
+#include <AnKi/Gr/Vulkan/VkFrameGarbageCollector.h>
 
 
 namespace anki {
 namespace anki {
 
 
@@ -108,7 +109,7 @@ TextureImpl::~TextureImpl()
 
 
 	garbage->m_memoryHandle = m_memHandle;
 	garbage->m_memoryHandle = m_memHandle;
 
 
-	getGrManagerImpl().getFrameGarbageCollector().newTextureGarbage(garbage);
+	VulkanFrameGarbageCollector::getSingleton().newTextureGarbage(garbage);
 }
 }
 
 
 Error TextureImpl::initInternal(VkImage externalImage, const TextureInitInfo& init_)
 Error TextureImpl::initInternal(VkImage externalImage, const TextureInitInfo& init_)

+ 12 - 1
AnKi/Renderer/FinalComposite.cpp

@@ -82,7 +82,18 @@ void FinalComposite::populateRenderGraph(RenderingContext& ctx)
 	GraphicsRenderPass& pass = rgraph.newGraphicsRenderPass("Final Composite");
 	GraphicsRenderPass& pass = rgraph.newGraphicsRenderPass("Final Composite");
 
 
 	const Bool bRendersToSwapchain = getRenderer().getSwapchainResolution() == getRenderer().getPostProcessResolution();
 	const Bool bRendersToSwapchain = getRenderer().getSwapchainResolution() == getRenderer().getPostProcessResolution();
-	const RenderTargetHandle outRt = (!bRendersToSwapchain) ? rgraph.newRenderTarget(m_rtDesc) : ctx.m_swapchainRenderTarget;
+	RenderTargetHandle outRt;
+	if(bRendersToSwapchain)
+	{
+		TexturePtr presentableTex = GrManager::getSingleton().acquireNextPresentableTexture();
+		outRt = ctx.m_renderGraphDescr.importRenderTarget(presentableTex.get(), TextureUsageBit::kNone);
+		ANKI_ASSERT(!ctx.m_swapchainRenderTarget.isValid());
+		ctx.m_swapchainRenderTarget = outRt;
+	}
+	else
+	{
+		outRt = rgraph.newRenderTarget(m_rtDesc);
+	}
 
 
 	if(!bRendersToSwapchain)
 	if(!bRendersToSwapchain)
 	{
 	{

+ 7 - 2
AnKi/Renderer/Renderer.cpp

@@ -758,7 +758,7 @@ void Renderer::updatePipelineStats()
 }
 }
 #endif
 #endif
 
 
-Error Renderer::render(Texture* presentTex)
+Error Renderer::render()
 {
 {
 	ANKI_TRACE_SCOPED_EVENT(Render);
 	ANKI_TRACE_SCOPED_EVENT(Render);
 
 
@@ -769,7 +769,6 @@ Error Renderer::render(Texture* presentTex)
 
 
 	RenderingContext ctx(&m_framePool);
 	RenderingContext ctx(&m_framePool);
 	ctx.m_renderGraphDescr.setStatisticsEnabled(ANKI_STATS_ENABLED);
 	ctx.m_renderGraphDescr.setStatisticsEnabled(ANKI_STATS_ENABLED);
-	ctx.m_swapchainRenderTarget = ctx.m_renderGraphDescr.importRenderTarget(presentTex, TextureUsageBit::kNone);
 
 
 #if ANKI_STATS_ENABLED
 #if ANKI_STATS_ENABLED
 	updatePipelineStats();
 	updatePipelineStats();
@@ -821,6 +820,12 @@ Error Renderer::render(Texture* presentTex)
 	ANKI_CHECK(populateRenderGraph(ctx));
 	ANKI_CHECK(populateRenderGraph(ctx));
 
 
 	// Blit renderer's result to swapchain
 	// Blit renderer's result to swapchain
+	if(!ctx.m_swapchainRenderTarget.isValid())
+	{
+		TexturePtr presentableTex = GrManager::getSingleton().acquireNextPresentableTexture();
+		ctx.m_swapchainRenderTarget = ctx.m_renderGraphDescr.importRenderTarget(presentableTex.get(), TextureUsageBit::kNone);
+	}
+
 	const Bool bNeedsBlit = m_postProcessResolution != m_swapchainResolution;
 	const Bool bNeedsBlit = m_postProcessResolution != m_swapchainResolution;
 	if(bNeedsBlit)
 	if(bNeedsBlit)
 	{
 	{

+ 1 - 1
AnKi/Renderer/Renderer.h

@@ -93,7 +93,7 @@ public:
 
 
 	Error init(const RendererInitInfo& inf);
 	Error init(const RendererInitInfo& inf);
 
 
-	Error render(Texture* presentTex);
+	Error render();
 
 
 #define ANKI_RENDERER_OBJECT_DEF(type, name, initCondition) \
 #define ANKI_RENDERER_OBJECT_DEF(type, name, initCondition) \
 	type& get##type() \
 	type& get##type() \

+ 9 - 3
Tests/Gr/Gr.cpp

@@ -89,6 +89,8 @@ ANKI_TEST(Gr, ClearScreen)
 		HighRezTimer timer;
 		HighRezTimer timer;
 		timer.start();
 		timer.start();
 
 
+		GrManager::getSingleton().beginFrame();
+
 		TexturePtr presentTex = GrManager::getSingleton().acquireNextPresentableTexture();
 		TexturePtr presentTex = GrManager::getSingleton().acquireNextPresentableTexture();
 
 
 		CommandBufferInitInfo cinit;
 		CommandBufferInitInfo cinit;
@@ -113,7 +115,7 @@ ANKI_TEST(Gr, ClearScreen)
 		cmdb->endRecording();
 		cmdb->endRecording();
 		GrManager::getSingleton().submit(cmdb.get());
 		GrManager::getSingleton().submit(cmdb.get());
 
 
-		GrManager::getSingleton().swapBuffers();
+		GrManager::getSingleton().endFrame();
 
 
 		timer.stop();
 		timer.stop();
 		const F32 TICK = 1.0f / 30.0f;
 		const F32 TICK = 1.0f / 30.0f;
@@ -446,6 +448,8 @@ float4 main(float4 svPosition : SV_POSITION, float2 uv : TEXCOORDS, uint svPrimI
 			HighRezTimer timer;
 			HighRezTimer timer;
 			timer.start();
 			timer.start();
 
 
+			GrManager::getSingleton().beginFrame();
+
 			TexturePtr presentTex = GrManager::getSingleton().acquireNextPresentableTexture();
 			TexturePtr presentTex = GrManager::getSingleton().acquireNextPresentableTexture();
 
 
 			CommandBufferInitInfo cinit;
 			CommandBufferInitInfo cinit;
@@ -476,7 +480,7 @@ float4 main(float4 svPosition : SV_POSITION, float2 uv : TEXCOORDS, uint svPrimI
 			cmdb->endRecording();
 			cmdb->endRecording();
 			GrManager::getSingleton().submit(cmdb.get());
 			GrManager::getSingleton().submit(cmdb.get());
 
 
-			GrManager::getSingleton().swapBuffers();
+			GrManager::getSingleton().endFrame();
 
 
 			timer.stop();
 			timer.stop();
 			const Second kTick = 1.0f / 30.0f;
 			const Second kTick = 1.0f / 30.0f;
@@ -803,6 +807,8 @@ float4 main(VertOut i) : SV_TARGET0
 			HighRezTimer timer;
 			HighRezTimer timer;
 			timer.start();
 			timer.start();
 
 
+			GrManager::getSingleton().beginFrame();
+
 			TexturePtr presentTex = GrManager::getSingleton().acquireNextPresentableTexture();
 			TexturePtr presentTex = GrManager::getSingleton().acquireNextPresentableTexture();
 
 
 			CommandBufferInitInfo cinit;
 			CommandBufferInitInfo cinit;
@@ -852,7 +858,7 @@ float4 main(VertOut i) : SV_TARGET0
 			cmdb->endRecording();
 			cmdb->endRecording();
 			GrManager::getSingleton().submit(cmdb.get());
 			GrManager::getSingleton().submit(cmdb.get());
 
 
-			GrManager::getSingleton().swapBuffers();
+			GrManager::getSingleton().endFrame();
 
 
 			timer.stop();
 			timer.stop();
 			const Second kTick = 1.0f / 30.0f;
 			const Second kTick = 1.0f / 30.0f;

+ 4 - 1
Tests/Gr/GrAsyncCompute.cpp

@@ -219,6 +219,9 @@ float4 main(VertOut input) : SV_Target0
 		for(U32 i = 0; i < iterationCount; ++i)
 		for(U32 i = 0; i < iterationCount; ++i)
 		{
 		{
 			ANKI_TEST_EXPECT_NO_ERR(Input::getSingleton().handleEvents());
 			ANKI_TEST_EXPECT_NO_ERR(Input::getSingleton().handleEvents());
+
+			GrManager::getSingleton().beginFrame();
+
 			TexturePtr presentTex = GrManager::getSingleton().acquireNextPresentableTexture();
 			TexturePtr presentTex = GrManager::getSingleton().acquireNextPresentableTexture();
 
 
 			// Init command buffers
 			// Init command buffers
@@ -382,7 +385,7 @@ float4 main(VertOut input) : SV_Target0
 				GrManager::getSingleton().submit(blitCmdb.get(), {}, &finalFence);
 				GrManager::getSingleton().submit(blitCmdb.get(), {}, &finalFence);
 			}
 			}
 
 
-			GrManager::getSingleton().swapBuffers();
+			GrManager::getSingleton().endFrame();
 		}
 		}
 
 
 		finalFence->clientWait(kMaxSecond);
 		finalFence->clientWait(kMaxSecond);

+ 3 - 1
Tests/Gr/GrMeshShaders.cpp

@@ -222,6 +222,8 @@ float3 main(VertOut input) : SV_TARGET0
 
 
 		for(U32 i = 0; i < 100; ++i)
 		for(U32 i = 0; i < 100; ++i)
 		{
 		{
+			GrManager::getSingleton().beginFrame();
+
 			TexturePtr swapchainTex = GrManager::getSingleton().acquireNextPresentableTexture();
 			TexturePtr swapchainTex = GrManager::getSingleton().acquireNextPresentableTexture();
 
 
 			CommandBufferInitInfo cmdbinit;
 			CommandBufferInitInfo cmdbinit;
@@ -258,7 +260,7 @@ float3 main(VertOut input) : SV_TARGET0
 			cmdb->endRecording();
 			cmdb->endRecording();
 			GrManager::getSingleton().submit(cmdb.get());
 			GrManager::getSingleton().submit(cmdb.get());
 
 
-			GrManager::getSingleton().swapBuffers();
+			GrManager::getSingleton().endFrame();
 
 
 			HighRezTimer::sleep(1.0_sec / 60.0);
 			HighRezTimer::sleep(1.0_sec / 60.0);
 		}
 		}

+ 3 - 1
Tests/Ui/Ui.cpp

@@ -89,6 +89,8 @@ ANKI_TEST(Ui, Ui)
 			HighRezTimer timer;
 			HighRezTimer timer;
 			timer.start();
 			timer.start();
 
 
+			GrManager::getSingleton().beginFrame();
+
 			canvas->handleInput();
 			canvas->handleInput();
 			if(Input::getSingleton().getKey(KeyCode::kEscape))
 			if(Input::getSingleton().getKey(KeyCode::kEscape))
 			{
 			{
@@ -125,7 +127,7 @@ ANKI_TEST(Ui, Ui)
 			cmdb->endRecording();
 			cmdb->endRecording();
 			GrManager::getSingleton().submit(cmdb.get());
 			GrManager::getSingleton().submit(cmdb.get());
 
 
-			GrManager::getSingleton().swapBuffers();
+			GrManager::getSingleton().endFrame();
 			RebarTransientMemoryPool::getSingleton().endFrame();
 			RebarTransientMemoryPool::getSingleton().endFrame();
 
 
 			timer.stop();
 			timer.stop();