Browse Source

Finalize Vulkan

Panagiotis Christopoulos Charitos 4 months ago
parent
commit
ac8c4c8fb7

+ 36 - 39
AnKi/Core/App.cpp

@@ -111,18 +111,30 @@ void* App::statsAllocCallback(void* userData, void* ptr, PtrSize size, [[maybe_u
 	return out;
 }
 
-App::App(U32 argc, Char** argv, CString appName, AllocAlignedCallback allocCb, void* allocCbUserData)
+App::App(CString appName, AllocAlignedCallback allocCb, void* allocCbUserData)
 {
 	m_originalAllocCallback = allocCb;
 	m_originalAllocUserData = allocCbUserData;
 
-	if(!appName.isEmpty())
+	if(ANKI_STATS_ENABLED && g_displayStatsCVar > 1)
+	{
+		m_allocCallback = statsAllocCallback;
+		m_allocUserData = this;
+	}
+	else
 	{
-		appName = "unnamed app";
+		m_allocCallback = m_originalAllocCallback;
+		m_allocUserData = m_originalAllocUserData = allocCbUserData;
 	}
 
-	m_appName = static_cast<Char*>(allocCb(allocCbUserData, nullptr, appName.getLength() + 1, alignof(Char)));
-	strcpy(m_appName, appName.cstr());
+	DefaultMemoryPool::allocateSingleton(m_allocCallback, m_allocUserData);
+	CoreMemoryPool::allocateSingleton(m_allocCallback, m_allocUserData);
+
+	if(appName.isEmpty())
+	{
+		appName = "UnnamedApp";
+	}
+	m_appName = appName;
 }
 
 App::~App()
@@ -159,12 +171,11 @@ void App::cleanup()
 
 	m_settingsDir.destroy();
 	m_cacheDir.destroy();
+	m_appName.destroy();
 
 	CoreMemoryPool::freeSingleton();
 	DefaultMemoryPool::freeSingleton();
 
-	m_originalAllocCallback(m_originalAllocUserData, m_appName, 0, 0);
-
 	ANKI_CORE_LOGI("Application finished shutting down");
 }
 
@@ -173,13 +184,6 @@ Error App::init()
 	StatsSet::getSingleton().initFromMainThread();
 	Logger::getSingleton().enableVerbosity(g_verboseLogCVar);
 
-	AllocAlignedCallback allocCb = m_originalAllocCallback;
-	void* allocCbUserData = m_originalAllocUserData;
-	initMemoryCallbacks(allocCb, allocCbUserData);
-
-	DefaultMemoryPool::allocateSingleton(allocCb, allocCbUserData);
-	CoreMemoryPool::allocateSingleton(allocCb, allocCbUserData);
-
 	ANKI_CHECK(initDirs());
 
 	ANKI_CORE_LOGI("Initializing application. Build config: %s", kAnKiBuildConfigString);
@@ -239,8 +243,8 @@ Error App::init()
 	// Graphics API
 	//
 	GrManagerInitInfo grInit;
-	grInit.m_allocCallback = allocCb;
-	grInit.m_allocCallbackUserData = allocCbUserData;
+	grInit.m_allocCallback = m_allocCallback;
+	grInit.m_allocCallbackUserData = m_allocUserData;
 	grInit.m_cacheDirectory = m_cacheDir.toCString();
 	ANKI_CHECK(GrManager::allocateSingleton().init(grInit));
 
@@ -267,7 +271,7 @@ Error App::init()
 	// Physics
 	//
 	PhysicsWorld::allocateSingleton();
-	ANKI_CHECK(PhysicsWorld::getSingleton().init(allocCb, allocCbUserData));
+	ANKI_CHECK(PhysicsWorld::getSingleton().init(m_allocCallback, m_allocUserData));
 
 	//
 	// Resources
@@ -286,12 +290,12 @@ Error App::init()
 	g_dataPathsCVar = extraPaths;
 #endif
 
-	ANKI_CHECK(ResourceManager::allocateSingleton().init(allocCb, allocCbUserData));
+	ANKI_CHECK(ResourceManager::allocateSingleton().init(m_allocCallback, m_allocUserData));
 
 	//
 	// UI
 	//
-	ANKI_CHECK(UiManager::allocateSingleton().init(allocCb, allocCbUserData));
+	ANKI_CHECK(UiManager::allocateSingleton().init(m_allocCallback, m_allocUserData));
 
 	//
 	// GPU scene
@@ -303,20 +307,21 @@ Error App::init()
 	//
 	RendererInitInfo renderInit;
 	renderInit.m_swapchainSize = UVec2(NativeWindow::getSingleton().getWidth(), NativeWindow::getSingleton().getHeight());
-	renderInit.m_allocCallback = allocCb;
-	renderInit.m_allocCallbackUserData = allocCbUserData;
+	renderInit.m_allocCallback = m_allocCallback;
+	renderInit.m_allocCallbackUserData = m_allocUserData;
 	ANKI_CHECK(Renderer::allocateSingleton().init(renderInit));
 
 	//
 	// Script
 	//
-	ScriptManager::allocateSingleton(allocCb, allocCbUserData);
+	ScriptManager::allocateSingleton(m_allocCallback, m_allocUserData);
 
 	//
 	// Scene
 	//
-	ANKI_CHECK(SceneGraph::allocateSingleton().init(allocCb, allocCbUserData));
+	ANKI_CHECK(SceneGraph::allocateSingleton().init(m_allocCallback, m_allocUserData));
 
+	GrManager::getSingleton().finish();
 	ANKI_CORE_LOGI("Application initialized");
 
 	return Error::kNone;
@@ -367,6 +372,13 @@ Error App::mainLoop()
 {
 	// Initialize the application
 	Error err = Error::kNone;
+	if((err = userPreInit()))
+	{
+		ANKI_CORE_LOGE("User initialization failed. Shutting down");
+		cleanup();
+		return err;
+	}
+
 	if((err = init()))
 	{
 		ANKI_CORE_LOGE("App initialization failed. Shutting down");
@@ -374,14 +386,12 @@ Error App::mainLoop()
 		return err;
 	}
 
-	GrManager::getSingleton().beginFrame();
-	if((err = userInit()))
+	if((err = userPostInit()))
 	{
 		ANKI_CORE_LOGE("User initialization failed. Shutting down");
 		cleanup();
 		return err;
 	}
-	GrManager::getSingleton().endFrame();
 
 	// Continue with the main loop
 	ANKI_CORE_LOGI("Entering main loop");
@@ -512,19 +522,6 @@ Error App::mainLoop()
 	return Error::kNone;
 }
 
-void App::initMemoryCallbacks(AllocAlignedCallback& allocCb, void*& allocCbUserData)
-{
-	if(ANKI_STATS_ENABLED && g_displayStatsCVar > 1)
-	{
-		allocCb = statsAllocCallback;
-		allocCbUserData = this;
-	}
-	else
-	{
-		// Leave the default
-	}
-}
-
 Bool App::toggleDeveloperConsole()
 {
 	SceneNode& node = SceneGraph::getSingleton().findSceneNode("_DevConsole");

+ 15 - 6
AnKi/Core/App.h

@@ -39,7 +39,7 @@ inline StatCounter g_cpuTotalTimeStatVar(StatCategory::kTime, "CPU total",
 class App
 {
 public:
-	App(U32 argc, Char** argv, CString applicationName, AllocAlignedCallback allocCb = allocAligned, void* allocCbUserData = nullptr);
+	App(CString applicationName, AllocAlignedCallback allocCb = allocAligned, void* allocCbUserData = nullptr);
 
 	virtual ~App();
 
@@ -56,8 +56,17 @@ public:
 	/// Run the main loop.
 	Error mainLoop();
 
-	/// User defined init code that will execute after all subsystems have initialized.
-	virtual Error userInit()
+	/// User defined init code that will execute before all subsystems have initialized. Will be executed just before the main loop. Useful for
+	/// setting cvars.
+	virtual Error userPreInit()
+	{
+		// Do nothing
+		return Error::kNone;
+	}
+
+	/// User defined init code that will execute after all subsystems have initialized. Will be executed just before the main loop and after
+	/// everything has been initialized.
+	virtual Error userPostInit()
 	{
 		// Do nothing
 		return Error::kNone;
@@ -86,15 +95,15 @@ private:
 	Bool m_consoleEnabled = false;
 	CoreString m_settingsDir; ///< The path that holds the configuration
 	CoreString m_cacheDir; ///< This is used as a cache
-	Char* m_appName = nullptr;
+	CoreString m_appName;
 
 	void* m_originalAllocUserData = nullptr;
 	AllocAlignedCallback m_originalAllocCallback = nullptr;
+	void* m_allocUserData = nullptr;
+	AllocAlignedCallback m_allocCallback = nullptr;
 
 	static void* statsAllocCallback(void* userData, void* ptr, PtrSize size, PtrSize alignment);
 
-	void initMemoryCallbacks(AllocAlignedCallback& allocCb, void*& allocCbUserData);
-
 	Error init();
 
 	Error initDirs();

+ 10 - 2
AnKi/Gr/Common.h

@@ -102,19 +102,27 @@ constexpr U32 kMaxFastConstantsSize = 128; ///< Push/root constants size. Thanks
 /// The number of commands in a command buffer that make it a small batch command buffer.
 constexpr U32 kCommandBufferSmallBatchMaxCommands = 100;
 
+// Smart pointers
 class GrObjectDeleter
 {
 public:
 	void operator()(GrObject* ptr);
 };
 
-// Smart pointer for objects
+class GrObjectDeleterInternal
+{
+public:
+	void operator()(GrObject* ptr);
+};
+
 using GrObjectPtr = IntrusivePtr<GrObject, GrObjectDeleter>;
+using GrObjectInternalPtr = IntrusivePtr<GrObject, GrObjectDeleterInternal>;
 
 #define ANKI_INSTANTIATE_GR_OBJECT(x_) \
 	class x_##Impl; \
 	class x_; \
-	using x_##Ptr = IntrusivePtr<x_, GrObjectDeleter>;
+	using x_##Ptr = IntrusivePtr<x_, GrObjectDeleter>; \
+	using x_##InternalPtr = IntrusivePtr<x_, GrObjectDeleterInternal>;
 #include <AnKi/Gr/BackendCommon/InstantiationMacros.def.h>
 
 #define ANKI_GR_OBJECT \

+ 6 - 6
AnKi/Gr/GrManager.h

@@ -46,17 +46,14 @@ public:
 	/// 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
-	/// dissapear even if you hold the reference.
+	/// Get next presentable image. The returned Texture is valid until the following swapBuffers. After that it might dissapear even if you hold the
+	/// reference.
 	TexturePtr acquireNextPresentableTexture();
 
 	/// End this frame.
 	void endFrame();
 
-	/// Wait for all work to finish.
-	void finish();
-
-	/// Finalize and submit if it's primary command buffer and just finalize if it's second level.
+	/// Submit command buffers. Can be called outside beginFrame() endFrame().
 	/// @param[in]  waitFences Optionally wait for some fences.
 	/// @param[out] signalFence Optionaly create fence that will be signaled when the submission is done.
 	void submit(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFences = {}, FencePtr* signalFence = nullptr);
@@ -66,6 +63,9 @@ public:
 		submit(WeakArray<CommandBuffer*>(&cmdb, 1), waitFences, signalFence);
 	}
 
+	/// Wait for all GPU work to finish.
+	void finish();
+
 	/// @name Object creation methods. They are thread-safe.
 	/// @{
 	[[nodiscard]] BufferPtr newBuffer(const BufferInitInfo& init);

+ 32 - 23
AnKi/Gr/RenderGraph.cpp

@@ -19,7 +19,7 @@ namespace anki {
 
 #define ANKI_DBG_RENDER_GRAPH 0
 
-static inline U32 getTextureSurfOrVolCount(const TexturePtr& tex)
+static inline U32 getTextureSurfOrVolCount(const TextureInternalPtr& tex)
 {
 	return tex->getMipmapCount() * tex->getLayerCount() * (textureTypeIsCube(tex->getTextureType()) ? 6 : 1);
 }
@@ -30,7 +30,7 @@ class RenderGraph::RT
 public:
 	DynamicArray<TextureUsageBit, MemoryPoolPtrWrapper<StackMemoryPool>> m_surfOrVolUsages;
 	DynamicArray<U16, MemoryPoolPtrWrapper<StackMemoryPool>> m_lastBatchThatTransitionedIt;
-	TexturePtr m_texture; ///< Hold a reference.
+	TextureInternalPtr m_texture; ///< Hold a reference.
 	Bool m_imported;
 
 	RT(StackMemoryPool* pool)
@@ -45,7 +45,7 @@ class RenderGraph::BufferRange
 {
 public:
 	BufferUsageBit m_usage;
-	BufferPtr m_buffer; ///< Hold a reference.
+	BufferInternalPtr m_buffer; ///< Hold a reference.
 	PtrSize m_offset;
 	PtrSize m_range;
 };
@@ -129,13 +129,13 @@ public:
 		U8 m_vrsTexelSizeY = 0;
 		Bool m_hasRenderpass = false;
 
-		Array<TexturePtr, kMaxColorRenderTargets + 2> m_refs;
+		Array<TextureInternalPtr, kMaxColorRenderTargets + 2> m_refs;
 	} m_beginRenderpassInfo;
 
 	BaseString<MemoryPoolPtrWrapper<StackMemoryPool>> m_name;
 
 	U32 m_batchIdx ANKI_DEBUG_CODE(= kMaxU32);
-	Bool m_drawsToPresentable = false;
+	Bool m_writesToSwapchain = false;
 
 	Pass(StackMemoryPool* pool)
 		: m_dependsOn(pool)
@@ -284,7 +284,7 @@ void RenderGraph::reset()
 
 	for(Pass& p : m_ctx->m_passes)
 	{
-		p.m_beginRenderpassInfo.m_refs.fill(TexturePtr(nullptr));
+		p.m_beginRenderpassInfo.m_refs.fill(TextureInternalPtr(nullptr));
 		p.m_callback.destroy();
 		p.m_name.destroy();
 	}
@@ -293,7 +293,7 @@ void RenderGraph::reset()
 	++m_version;
 }
 
-TexturePtr RenderGraph::getOrCreateRenderTarget(const TextureInitInfo& initInf, U64 hash)
+TextureInternalPtr RenderGraph::getOrCreateRenderTarget(const TextureInitInfo& initInf, U64 hash)
 {
 	ANKI_ASSERT(hash);
 
@@ -314,7 +314,7 @@ TexturePtr RenderGraph::getOrCreateRenderTarget(const TextureInitInfo& initInf,
 	ANKI_ASSERT(entry);
 
 	// Create or pop one tex from the cache
-	TexturePtr tex;
+	TextureInternalPtr tex;
 	const Bool createNewTex = entry->m_textures.getSize() == entry->m_texturesInUse;
 	if(!createNewTex)
 	{
@@ -599,6 +599,7 @@ void RenderGraph::initRenderPassesAndSetDeps(const RenderGraphBuilder& descr)
 
 		outPass.m_callback = inPass.m_callback;
 		outPass.m_name = inPass.m_name;
+		outPass.m_writesToSwapchain = inPass.m_writesToSwapchain;
 
 		// Create consumer info
 		outPass.m_consumedTextures.resize(inPass.m_rtDeps.getSize());
@@ -1104,6 +1105,7 @@ void RenderGraph::recordAndSubmitCommandBuffers(FencePtr* optionalFence)
 	DynamicArray<CommandBufferPtr, MemoryPoolPtrWrapper<StackMemoryPool>> cmdbs(pool);
 	cmdbs.resize(batchGroupCount);
 	SpinLock cmdbsMtx;
+	Atomic<U32> firstGroupThatWroteToSwapchain(kMaxU32);
 
 	for(U32 group = 0; group < batchGroupCount; ++group)
 	{
@@ -1116,7 +1118,7 @@ void RenderGraph::recordAndSubmitCommandBuffers(FencePtr* optionalFence)
 		}
 
 		CoreThreadJobManager::getSingleton().dispatchTask(
-			[this, start, end, pool, &cmdbs, &cmdbsMtx, group, batchGroupCount]([[maybe_unused]] U32 tid) {
+			[this, start, end, pool, &cmdbs, &cmdbsMtx, group, batchGroupCount, &firstGroupThatWroteToSwapchain]([[maybe_unused]] U32 tid) {
 				ANKI_TRACE_SCOPED_EVENT(GrRenderGraphTask);
 
 				Array<Char, 32> name;
@@ -1128,7 +1130,7 @@ void RenderGraph::recordAndSubmitCommandBuffers(FencePtr* optionalFence)
 				// Write timestamp
 				const Bool setPreQuery = m_ctx->m_gatherStatistics && group == 0;
 				const Bool setPostQuery = m_ctx->m_gatherStatistics && group == batchGroupCount - 1;
-				TimestampQueryPtr preQuery, postQuery;
+				TimestampQueryInternalPtr preQuery, postQuery;
 				if(setPreQuery)
 				{
 					preQuery = GrManager::getSingleton().newTimestampQuery();
@@ -1213,6 +1215,11 @@ void RenderGraph::recordAndSubmitCommandBuffers(FencePtr* optionalFence)
 					{
 						Pass& pass = m_ctx->m_passes[passIdx];
 
+						if(pass.m_writesToSwapchain)
+						{
+							firstGroupThatWroteToSwapchain.min(group);
+						}
+
 						const Vec3 passColor = (pass.m_beginRenderpassInfo.m_hasRenderpass) ? Vec3(0.0f, 1.0f, 0.0f) : Vec3(1.0f, 1.0f, 0.0f);
 						cmdb->pushDebugMarker(pass.m_name, passColor);
 
@@ -1253,24 +1260,26 @@ void RenderGraph::recordAndSubmitCommandBuffers(FencePtr* optionalFence)
 	CoreThreadJobManager::getSingleton().waitForAllTasksToFinish();
 
 	// Submit
-	if(cmdbs.getSize() == 1) [[unlikely]]
+	DynamicArray<CommandBuffer*, MemoryPoolPtrWrapper<StackMemoryPool>> pCmdbs(pool);
+	pCmdbs.resize(cmdbs.getSize());
+	for(U32 i = 0; i < cmdbs.getSize(); ++i)
+	{
+		pCmdbs[i] = cmdbs[i].get();
+	}
+
+	const U32 firstGroupThatWroteToSwapchain2 = firstGroupThatWroteToSwapchain.getNonAtomically();
+	if(firstGroupThatWroteToSwapchain2 == 0 || firstGroupThatWroteToSwapchain2 == kMaxU32)
 	{
-		GrManager::getSingleton().submit(cmdbs[0].get(), {}, optionalFence);
+		GrManager::getSingleton().submit(WeakArray(pCmdbs), {}, optionalFence);
 	}
 	else
 	{
-		// 2 submits. The 1st contains all the batches minus the last. Then the last batch is alone given that it most likely it writes to the
-		// swapchain
+		// 2 submits. The 1st contains all the batches that don't write to swapchain
 
-		DynamicArray<CommandBuffer*, MemoryPoolPtrWrapper<StackMemoryPool>> pCmdbs(pool);
-		pCmdbs.resize(cmdbs.getSize() - 1);
-		for(U32 i = 0; i < cmdbs.getSize() - 1; ++i)
-		{
-			pCmdbs[i] = cmdbs[i].get();
-		}
+		GrManager::getSingleton().submit(WeakArray(pCmdbs).subrange(0, firstGroupThatWroteToSwapchain2), {}, nullptr);
 
-		GrManager::getSingleton().submit(WeakArray(pCmdbs), {}, nullptr);
-		GrManager::getSingleton().submit(cmdbs.getBack().get(), {}, optionalFence);
+		GrManager::getSingleton().submit(
+			WeakArray(pCmdbs).subrange(firstGroupThatWroteToSwapchain2, batchGroupCount - firstGroupThatWroteToSwapchain2), {}, optionalFence);
 	}
 }
 
@@ -1304,7 +1313,7 @@ void RenderGraph::periodicCleanup()
 			rtsCleanedCount += entry.m_textures.getSize() - entry.m_texturesInUse;
 
 			// New array
-			GrDynamicArray<TexturePtr> newArray;
+			GrDynamicArray<TextureInternalPtr> newArray;
 			if(entry.m_texturesInUse > 0)
 			{
 				newArray.resize(entry.m_texturesInUse);

+ 13 - 6
AnKi/Gr/RenderGraph.h

@@ -68,7 +68,7 @@ class RenderTargetHandle : public RenderGraphGrObjectHandle
 {
 };
 
-/// BufferPtr handle.
+/// Buffer handle.
 /// @memberof RenderGraphBuilder
 class BufferHandle : public RenderGraphGrObjectHandle
 {
@@ -284,6 +284,11 @@ public:
 		newDependency<RenderPassDependency::Type::kAccelerationStructure>(RenderPassDependency(handle, usage));
 	}
 
+	void setWritesToSwapchain()
+	{
+		m_writesToSwapchain = true;
+	}
+
 protected:
 	enum class Type : U8
 	{
@@ -310,6 +315,8 @@ protected:
 
 	BaseString<MemoryPoolPtrWrapper<StackMemoryPool>> m_name;
 
+	Bool m_writesToSwapchain = false;
+
 	RenderPassBase(Type t, RenderGraphBuilder* descr, StackMemoryPool* pool)
 		: m_type(t)
 		, m_descr(descr)
@@ -464,7 +471,7 @@ private:
 	public:
 		TextureInitInfo m_initInfo;
 		U64 m_hash = 0;
-		TexturePtr m_importedTex;
+		TextureInternalPtr m_importedTex;
 		TextureUsageBit m_importedLastKnownUsage = TextureUsageBit::kNone;
 		/// Derived by the deps of this RT and will be used to set its usage.
 		TextureUsageBit m_usageDerivedByDeps = TextureUsageBit::kNone;
@@ -475,7 +482,7 @@ private:
 	{
 	public:
 		BufferUsageBit m_usage;
-		BufferPtr m_importedBuff;
+		BufferInternalPtr m_importedBuff;
 		PtrSize m_offset;
 		PtrSize m_range;
 	};
@@ -552,7 +559,7 @@ private:
 	class RenderTargetCacheEntry
 	{
 	public:
-		GrDynamicArray<TexturePtr> m_textures;
+		GrDynamicArray<TextureInternalPtr> m_textures;
 		U32 m_texturesInUse = 0;
 	};
 
@@ -573,7 +580,7 @@ private:
 	class
 	{
 	public:
-		Array2d<TimestampQueryPtr, kMaxBufferedTimestamps, 2> m_timestamps;
+		Array2d<TimestampQueryInternalPtr, kMaxBufferedTimestamps, 2> m_timestamps;
 		Array<Second, kMaxBufferedTimestamps> m_cpuStartTimes;
 		U8 m_nextTimestamp = 0;
 	} m_statistics;
@@ -593,7 +600,7 @@ private:
 	void minimizeSubchannelSwitches();
 	void sortBatchPasses();
 
-	TexturePtr getOrCreateRenderTarget(const TextureInitInfo& initInf, U64 hash);
+	TextureInternalPtr getOrCreateRenderTarget(const TextureInitInfo& initInf, U64 hash);
 
 	/// Every N number of frames clean unused cached items.
 	void periodicCleanup();

+ 0 - 11
AnKi/Gr/Vulkan/VkCommon.h

@@ -47,17 +47,6 @@ class GrManagerImpl;
 #define ANKI_VK_SELF(class_) class_& self = *static_cast<class_*>(this)
 #define ANKI_VK_SELF_CONST(class_) const class_& self = *static_cast<const class_*>(this)
 
-class GrObjectDeleterInternal
-{
-public:
-	void operator()(GrObject* ptr);
-};
-
-using GrObjectInternalPtr = IntrusivePtr<GrObject, GrObjectDeleterInternal>;
-
-#define ANKI_INSTANTIATE_GR_OBJECT(x) using x##InternalPtr = IntrusivePtr<x, GrObjectDeleterInternal>;
-#include <AnKi/Gr/BackendCommon/InstantiationMacros.def.h>
-
 ANKI_PURE GrManagerImpl& getGrManagerImpl();
 ANKI_PURE VkDevice getVkDevice();
 

+ 72 - 66
AnKi/Gr/Vulkan/VkGrManager.cpp

@@ -3,7 +3,6 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include "AnKi/Util/Tracer.h"
 #include <AnKi/Gr/Vulkan/VkGrManager.h>
 #include <AnKi/Util/StringList.h>
 #include <AnKi/Core/App.h>
@@ -103,7 +102,7 @@ void GrManager::endFrame()
 void GrManager::finish()
 {
 	ANKI_VK_SELF(GrManagerImpl);
-	self.finish();
+	self.finishInternal();
 }
 
 #define ANKI_NEW_GR_OBJECT(type) \
@@ -154,32 +153,10 @@ GrManagerImpl::~GrManagerImpl()
 {
 	ANKI_VK_LOGI("Destroying Vulkan backend");
 
-	// 1st THING: wait for all fences
-	for(PerFrame& frame : m_perFrame)
-	{
-		for(MicroFencePtr& fence : frame.m_fences)
-		{
-			if(fence)
-			{
-				fence->clientWait(kMaxSecond);
-			}
-		}
-
-		frame.m_fences.destroy();
-	}
-
-	// 2nd THING: wait for the GPU
-	for(VkQueue& queue : m_queues)
-	{
-		LockGuard<Mutex> lock(m_globalMtx);
-		if(queue)
-		{
-			vkQueueWaitIdle(queue);
-			queue = VK_NULL_HANDLE;
-		}
-	}
+	// 1st THING: wait for the GPU
+	finishInternal();
 
-	// 3rd THING: The destroy everything that has a reference to GrObjects.
+	// 2nd THING: The destroy everything that has a reference to GrObjects.
 	m_crntSwapchain.reset(nullptr);
 	SwapchainFactory::freeSingleton();
 
@@ -189,12 +166,12 @@ GrManagerImpl::~GrManagerImpl()
 		deleteObjectsMarkedForDeletion();
 	}
 
-	// 4th THING: Continue with the rest
+	// 3rd THING: Continue with the rest
 	CommandBufferFactory::freeSingleton();
 	OcclusionQueryFactory::freeSingleton();
 	TimestampQueryFactory::freeSingleton();
 	PrimitivesPassedClippingFactory::freeSingleton();
-	SemaphoreFactory::freeSingleton(); // Destroy before fences
+	SemaphoreFactory::freeSingleton();
 	SamplerFactory::freeSingleton();
 
 	GpuMemoryManager::freeSingleton();
@@ -1248,7 +1225,6 @@ void GrManagerImpl::beginFrameInternal()
 			if(fence)
 			{
 				const Bool signaled = fence->clientWait(kMaxSecond);
-				ANKI_LOGI("Waited for %s", fence->getName().cstr());
 				if(!signaled)
 				{
 					ANKI_VK_LOGF("Timeout detected");
@@ -1310,6 +1286,7 @@ TexturePtr GrManagerImpl::acquireNextPresentableTexture()
 		ANKI_VK_CHECKF(res);
 	}
 
+	ANKI_ASSERT(m_acquiredImageIdx == kMaxU8 && "Tried to acquire multiple times in a frame?");
 	m_acquiredImageIdx = U8(imageIdx);
 	return TexturePtr(m_crntSwapchain->m_textures[imageIdx].get());
 }
@@ -1321,45 +1298,54 @@ void GrManagerImpl::endFrameInternal()
 	LockGuard<Mutex> lock(m_globalMtx);
 
 	PerFrame& frame = m_perFrame[m_frame % m_perFrame.getSize()];
-
-	ANKI_ASSERT(m_frameState == kPresentableDrawn || m_frameState == kFrameStarted);
-	const Bool drawToPresentable = m_frameState == kPresentableDrawn;
-	m_frameState = kFrameEnded;
+	const Bool imageAcquired = m_acquiredImageIdx < kMaxU8;
 
 	// Present
-	VkResult res;
-	VkPresentInfoKHR present = {};
-	present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
-	present.waitSemaphoreCount = (drawToPresentable) ? 1 : 0;
-	present.pWaitSemaphores =
-		(drawToPresentable) ? &m_crntSwapchain->m_renderSemaphores[m_frame % m_crntSwapchain->m_renderSemaphores.getSize()]->getHandle() : nullptr;
-	present.swapchainCount = 1;
-	present.pSwapchains = &m_crntSwapchain->m_swapchain;
-	const U32 idx = m_acquiredImageIdx;
-	present.pImageIndices = &idx;
-	present.pResults = &res;
-
-	const VkResult res1 = vkQueuePresentKHR(m_queues[frame.m_queueWroteToSwapchainImage], &present);
-	if(res1 == VK_ERROR_OUT_OF_DATE_KHR)
-	{
-		ANKI_VK_LOGW("Swapchain is out of date. Will wait for the queues and create a new one");
-		for(VkQueue queue : m_queues)
+	if(imageAcquired)
+	{
+		ANKI_ASSERT(m_frameState == kPresentableDrawn && "Acquired an image but didn't draw to it?");
+		ANKI_ASSERT(m_acquiredImageIdx < kMaxU8);
+
+		VkResult res;
+		VkPresentInfoKHR present = {};
+		present.sType = VK_STRUCTURE_TYPE_PRESENT_INFO_KHR;
+		present.waitSemaphoreCount = 1;
+		present.pWaitSemaphores = &m_crntSwapchain->m_renderSemaphores[m_acquiredImageIdx]->getHandle();
+		present.swapchainCount = 1;
+		present.pSwapchains = &m_crntSwapchain->m_swapchain;
+		const U32 idx = m_acquiredImageIdx;
+		present.pImageIndices = &idx;
+		present.pResults = &res;
+
+		const VkResult res1 = vkQueuePresentKHR(m_queues[frame.m_queueWroteToSwapchainImage], &present);
+		if(res1 == VK_ERROR_OUT_OF_DATE_KHR)
 		{
-			if(queue)
+			ANKI_VK_LOGW("Swapchain is out of date. Will wait for the queues and create a new one");
+			for(VkQueue queue : m_queues)
 			{
-				vkQueueWaitIdle(queue);
+				if(queue)
+				{
+					vkQueueWaitIdle(queue);
+				}
 			}
+			vkDeviceWaitIdle(m_device);
+			m_crntSwapchain.reset(nullptr);
+			m_crntSwapchain = SwapchainFactory::getSingleton().newInstance();
 		}
-		vkDeviceWaitIdle(m_device);
-		m_crntSwapchain.reset(nullptr);
-		m_crntSwapchain = SwapchainFactory::getSingleton().newInstance();
+		else
+		{
+			ANKI_VK_CHECKF(res1);
+			ANKI_VK_CHECKF(res);
+		}
+
+		m_acquiredImageIdx = kMaxU8;
 	}
 	else
 	{
-		ANKI_VK_CHECKF(res1);
-		ANKI_VK_CHECKF(res);
+		ANKI_ASSERT(m_frameState == kFrameStarted);
 	}
 
+	m_frameState = kFrameEnded;
 	GpuMemoryManager::getSingleton().updateStats();
 }
 
@@ -1435,12 +1421,13 @@ void GrManagerImpl::submitInternal(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fe
 
 		// Do some special stuff for the last command buffer
 		GrManagerImpl::PerFrame& frame = m_perFrame[m_frame % m_perFrame.getSize()];
-		const MicroSemaphore& acquireSemaphore = *m_crntSwapchain->m_acquireSemaphores[m_frame % m_crntSwapchain->m_acquireSemaphores.getSize()];
 		if(renderedToDefaultFb)
 		{
 			ANKI_ASSERT(m_frameState == kPresentableAcquired);
 			m_frameState = kPresentableDrawn;
 
+			const MicroSemaphore& acquireSemaphore = *m_crntSwapchain->m_acquireSemaphores[m_frame % m_crntSwapchain->m_acquireSemaphores.getSize()];
+
 			// Wait semaphore
 			waitSemaphores.emplaceBack(acquireSemaphore.getHandle());
 
@@ -1451,7 +1438,7 @@ void GrManagerImpl::submitInternal(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fe
 			waitTimelineValues.emplaceBack(0);
 
 			// Get the semaphore to signal and then wait on present
-			const MicroSemaphore& renderSemaphore = *m_crntSwapchain->m_renderSemaphores[m_frame & m_crntSwapchain->m_renderSemaphores.getSize()];
+			const MicroSemaphore& renderSemaphore = *m_crntSwapchain->m_renderSemaphores[m_acquiredImageIdx];
 			signalSemaphores.emplaceBack(renderSemaphore.getHandle());
 
 			// Increment the timeline values as well because the spec wants a dummy value even for non-timeline semaphores
@@ -1459,10 +1446,6 @@ void GrManagerImpl::submitInternal(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fe
 
 			frame.m_queueWroteToSwapchainImage = queueType;
 		}
-		else
-		{
-			ANKI_ASSERT(m_frameState == kFrameStarted);
-		}
 
 		frame.m_fences.emplaceBack(fence);
 
@@ -1490,7 +1473,7 @@ void GrManagerImpl::submitInternal(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fe
 	}
 }
 
-void GrManagerImpl::finish()
+void GrManagerImpl::finishInternal()
 {
 	LockGuard<Mutex> lock(m_globalMtx);
 	for(VkQueue queue : m_queues)
@@ -1500,6 +1483,29 @@ void GrManagerImpl::finish()
 			vkQueueWaitIdle(queue);
 		}
 	}
+
+	for(PerFrame& frame : m_perFrame)
+	{
+		for(MicroFencePtr& fence : frame.m_fences)
+		{
+			const Bool signaled = fence->clientWait(kMaxSecond);
+			if(!signaled)
+			{
+				ANKI_VK_LOGF("Timeout detected");
+			}
+		}
+
+		frame.m_fences.destroy();
+	}
+
+	// Since we waited for the GPU do a cleanup as well
+	const U64 oldFrame = m_frame;
+	for(U32 frame = 0; frame < m_perFrame.getSize(); ++frame)
+	{
+		m_frame = frame;
+		deleteObjectsMarkedForDeletion();
+	}
+	m_frame = oldFrame;
 }
 
 void GrManagerImpl::trySetVulkanHandleName(CString name, VkObjectType type, U64 handle) const
@@ -1681,7 +1687,7 @@ VkBool32 GrManagerImpl::debugReportCallbackEXT(VkDebugUtilsMessageSeverityFlagBi
 
 	if(messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_ERROR_BIT_EXT)
 	{
-		ANKI_VK_LOGI("VK debug report: %s. Affected objects: %s", pCallbackData->pMessage, objectNames.cstr()); // TODO
+		ANKI_VK_LOGE("VK debug report: %s. Affected objects: %s", pCallbackData->pMessage, objectNames.cstr());
 	}
 	else if(messageSeverity & VK_DEBUG_UTILS_MESSAGE_SEVERITY_WARNING_BIT_EXT)
 	{

+ 1 - 2
AnKi/Gr/Vulkan/VkGrManager.h

@@ -5,7 +5,6 @@
 
 #pragma once
 
-#include "AnKi/Util/Tracer.h"
 #include <AnKi/Gr/GrManager.h>
 #include <AnKi/Gr/Vulkan/VkCommon.h>
 #include <AnKi/Gr/Vulkan/VkSemaphoreFactory.h>
@@ -86,7 +85,7 @@ public:
 
 	void submitInternal(WeakArray<CommandBuffer*> cmdbs, WeakArray<Fence*> waitFences, FencePtr* signalFence);
 
-	void finish();
+	void finishInternal();
 
 	VkDevice getDevice() const
 	{

+ 2 - 0
AnKi/Renderer/FinalComposite.cpp

@@ -89,6 +89,8 @@ void FinalComposite::populateRenderGraph(RenderingContext& ctx)
 		outRt = ctx.m_renderGraphDescr.importRenderTarget(presentableTex.get(), TextureUsageBit::kNone);
 		ANKI_ASSERT(!ctx.m_swapchainRenderTarget.isValid());
 		ctx.m_swapchainRenderTarget = outRt;
+
+		pass.setWritesToSwapchain();
 	}
 	else
 	{

+ 1 - 2
AnKi/Renderer/HistoryLength.h

@@ -5,7 +5,6 @@
 
 #pragma once
 
-#include "AnKi/Gr/RenderGraph.h"
 #include <AnKi/Renderer/RendererObject.h>
 
 namespace anki {
@@ -13,7 +12,7 @@ namespace anki {
 /// @addtogroup renderer
 /// @{
 
-/// XXX
+/// Compute the history length (aka disocclusion length)
 class HistoryLength : public RendererObject
 {
 public:

+ 0 - 1
AnKi/Renderer/IndirectDiffuseClipmaps.cpp

@@ -3,7 +3,6 @@
 // Code licensed under the BSD License.
 // http://www.anki3d.org/LICENSE
 
-#include "AnKi/Gr/RenderGraph.h"
 #include <AnKi/Renderer/IndirectDiffuseClipmaps.h>
 #include <AnKi/Renderer/Renderer.h>
 #include <AnKi/Renderer/GBuffer.h>

+ 1 - 0
AnKi/Renderer/Renderer.cpp

@@ -831,6 +831,7 @@ Error Renderer::render()
 	{
 		GraphicsRenderPass& pass = ctx.m_renderGraphDescr.newGraphicsRenderPass("Final Blit");
 		pass.setRenderpassInfo({GraphicsRenderPassTargetDesc(ctx.m_swapchainRenderTarget)});
+		pass.setWritesToSwapchain();
 
 		pass.newTextureDependency(ctx.m_swapchainRenderTarget, TextureUsageBit::kRtvDsvWrite);
 		pass.newTextureDependency(m_finalComposite->getRenderTarget(), TextureUsageBit::kSrvPixel);

+ 2 - 2
AnKi/Util/CVarSet.cpp

@@ -41,7 +41,7 @@ Error CVarSet::setMultiple(ConstWeakArray<const Char*> arr)
 		++i;
 		if(i >= arr.getSize())
 		{
-			ANKI_UTIL_LOGE("Expecting a command line argument after %s", varName.cstr());
+			ANKI_UTIL_LOGE("Expecting a value after %s", varName.cstr());
 			return Error::kUserData;
 		}
 		ANKI_ASSERT(arr[i]);
@@ -59,7 +59,7 @@ Error CVarSet::setMultiple(ConstWeakArray<const Char*> arr)
 			{
 				if(foundCVar)
 				{
-					ANKI_UTIL_LOGE("Command line arg %s has ambiguous name. Skipping", varName.cstr());
+					ANKI_UTIL_LOGE("Arg %s has ambiguous name. Skipping", varName.cstr());
 				}
 				else
 				{

+ 0 - 2
AnKi/Util/Singleton.h

@@ -5,10 +5,8 @@
 
 #pragma once
 
-#include "AnKi/Config.h"
 #include <AnKi/Util/StdTypes.h>
 #include <AnKi/Util/Assert.h>
-#include <utility>
 
 namespace anki {
 

+ 34 - 2
AnKi/Util/WeakArray.h

@@ -35,8 +35,16 @@ public:
 		}
 	}
 
+	WeakArray(T* begin, T* end)
+		: m_data(begin)
+		, m_size(end - begin)
+	{
+		ANKI_ASSERT(begin && end);
+		ANKI_ASSERT(end >= begin);
+	}
+
 	WeakArray()
-		: WeakArray(nullptr, 0)
+		: WeakArray(nullptr, Size(0))
 	{
 	}
 
@@ -232,6 +240,14 @@ public:
 		}
 	}
 
+	WeakArray subrange(Size offset, Size range) const
+	{
+		ANKI_ASSERT(offset < m_size);
+		ANKI_ASSERT(offset + range <= m_size);
+		WeakArray out(m_data + offset, range);
+		return out;
+	}
+
 private:
 	Value* m_data;
 	Size m_size;
@@ -257,8 +273,16 @@ public:
 		}
 	}
 
+	ConstWeakArray(T* begin, T* end)
+		: m_data(begin)
+		, m_size(end - begin)
+	{
+		ANKI_ASSERT(begin && end);
+		ANKI_ASSERT(end >= begin);
+	}
+
 	ConstWeakArray()
-		: ConstWeakArray(nullptr, 0)
+		: ConstWeakArray(nullptr, Size(0))
 	{
 	}
 
@@ -408,6 +432,14 @@ public:
 		return m_size * sizeof(Value);
 	}
 
+	ConstWeakArray subrange(Size offset, Size range) const
+	{
+		ANKI_ASSERT(offset < m_size);
+		ANKI_ASSERT(offset + range <= m_size);
+		ConstWeakArray out(m_data + offset, range);
+		return out;
+	}
+
 private:
 	const Value* m_data;
 	Size m_size;

+ 5 - 9
Samples/Common/SampleApp.cpp

@@ -7,15 +7,14 @@
 
 using namespace anki;
 
-Error SampleApp::init(int argc, char** argv, CString sampleName)
+Error SampleApp::userPreInit()
 {
 	// Init the super class
 	g_windowFullscreenCVar = 1;
 
 #if !ANKI_OS_ANDROID
-	HeapMemoryPool tmpPool(allocAligned, nullptr);
-	BaseString<MemoryPoolPtrWrapper<HeapMemoryPool>> assetsDataPath(&tmpPool);
-	assetsDataPath.sprintf("%s/Samples/%s", ANKI_SOURCE_DIRECTORY, sampleName.cstr());
+	String assetsDataPath;
+	assetsDataPath.sprintf("%s/Samples/%s", ANKI_SOURCE_DIRECTORY, getApplicationName().cstr());
 
 	if(!directoryExists(assetsDataPath))
 	{
@@ -23,14 +22,11 @@ Error SampleApp::init(int argc, char** argv, CString sampleName)
 	}
 	else
 	{
-		g_dataPathsCVar = BaseString<MemoryPoolPtrWrapper<HeapMemoryPool>>(&tmpPool).sprintf("%s|.anki,lua", assetsDataPath.cstr());
+		g_dataPathsCVar = String().sprintf("%s|.anki,lua", assetsDataPath.cstr());
 	}
 #endif
 
-	ANKI_CHECK(CVarSet::getSingleton().setFromCommandLineArguments(argc - 1, argv + 1));
-	ANKI_CHECK(App::init());
-
-	ANKI_CHECK(sampleExtraInit());
+	ANKI_CHECK(CVarSet::getSingleton().setFromCommandLineArguments(m_argc - 1, m_argv + 1));
 
 	return Error::kNone;
 }

+ 16 - 2
Samples/Common/SampleApp.h

@@ -12,9 +12,23 @@ namespace anki {
 class SampleApp : public App
 {
 public:
-	using App::App;
+	U32 m_argc = 0;
+	Char** m_argv = nullptr;
+
+	SampleApp(U32 argc, Char** argv, CString appName)
+		: App(appName)
+		, m_argc(argc)
+		, m_argv(argv)
+	{
+	}
+
+	Error userPreInit() override;
+
+	Error userPostInit() override
+	{
+		return sampleExtraInit();
+	}
 
-	Error userInit(int argc, char** argv, CString sampleName);
 	Error userMainLoop(Bool& quit, Second elapsedTime) override;
 
 	virtual Error sampleExtraInit() = 0;

+ 5 - 10
Samples/PhysicsPlayground/Main.cpp

@@ -67,6 +67,8 @@ end
 class MyApp : public SampleApp
 {
 public:
+	using SampleApp::SampleApp;
+
 	Error sampleExtraInit() override;
 	Error userMainLoop(Bool& quit, Second elapsedTime) override;
 };
@@ -480,14 +482,9 @@ Error MyApp::userMainLoop(Bool& quit, [[maybe_unused]] Second elapsedTime)
 ANKI_MAIN_FUNCTION(myMain)
 int myMain(int argc, char* argv[])
 {
-	Error err = Error::kNone;
-
-	MyApp* app = new MyApp;
-	err = app->init(argc, argv, "PhysicsPlayground");
-	if(!err)
-	{
-		err = app->mainLoop();
-	}
+	MyApp* app = new MyApp(argc, argv, "PhysicsPlayground");
+	Error err = app->mainLoop();
+	delete app;
 
 	if(err)
 	{
@@ -498,7 +495,5 @@ int myMain(int argc, char* argv[])
 		ANKI_LOGI("Bye!!");
 	}
 
-	delete app;
-
 	return 0;
 }

+ 3 - 9
Samples/SimpleScene/Main.cpp

@@ -26,16 +26,10 @@ public:
 ANKI_MAIN_FUNCTION(myMain)
 int myMain(int argc, char* argv[])
 {
-	Error err = Error::kNone;
-
-	MyApp* app = new MyApp(allocAligned, nullptr);
-	err = app->init(argc, argv, "SimpleScene");
-	if(!err)
-	{
-		err = app->mainLoop();
-	}
-
+	MyApp* app = new MyApp(argc, argv, "SimpleScene");
+	const Error err = app->mainLoop();
 	delete app;
+
 	if(err)
 	{
 		ANKI_LOGE("Error reported. Bye!");

+ 2 - 2
Samples/SkeletalAnimation/Assets/Scene.lua

@@ -99,7 +99,7 @@ trf:setRotation(rot)
 trf:setScale(Vec3.new(9.000000, 9.000000, 9.000000))
 node:setLocalTransform(trf)
 
-node = scene:tryFindSceneNode("ArmL2")
+--[[node = scene:tryFindSceneNode("ArmL2")
 getEventManager():newAnimationEvent("Assets/float.001_ccb9eb33e30c8fa4.ankianim", "ArmL2", node)
 
 node = scene:tryFindSceneNode("Body")
@@ -127,4 +127,4 @@ node = scene:tryFindSceneNode("ArmR2")
 getEventManager():newAnimationEvent("Assets/wave_6cf284ed471bff3b.ankianim", "ArmR2", node)
 
 node = scene:tryFindSceneNode("ArmR1")
-getEventManager():newAnimationEvent("Assets/wave_6cf284ed471bff3b.ankianim", "ArmR1", node)
+getEventManager():newAnimationEvent("Assets/wave_6cf284ed471bff3b.ankianim", "ArmR1", node)]]

+ 3 - 9
Samples/SkeletalAnimation/Main.cpp

@@ -53,16 +53,10 @@ public:
 ANKI_MAIN_FUNCTION(myMain)
 int myMain(int argc, char* argv[])
 {
-	Error err = Error::kNone;
-
-	MyApp* app = new MyApp(allocAligned, nullptr);
-	err = app->init(argc, argv, "SkeletalAnimation");
-	if(!err)
-	{
-		err = app->mainLoop();
-	}
-
+	MyApp* app = new MyApp(argc, argv, "SkeletalAnimation");
+	const Error err = app->mainLoop();
 	delete app;
+
 	if(err)
 	{
 		ANKI_LOGE("Error reported. Bye!");

+ 2 - 6
Samples/Sponza/Main.cpp

@@ -28,12 +28,8 @@ int myMain(int argc, char* argv[])
 {
 	Error err = Error::kNone;
 
-	MyApp* app = new MyApp(allocAligned, nullptr);
-	err = app->init(argc, argv, "Sponza");
-	if(!err)
-	{
-		err = app->mainLoop();
-	}
+	MyApp* app = new MyApp(argc, argv, "Sponza");
+	err = app->mainLoop();
 
 	delete app;
 	if(err)

+ 23 - 21
Sandbox/Main.cpp

@@ -4,8 +4,6 @@
 // http://www.anki3d.org/LICENSE
 
 #include <cstdio>
-#include <iostream>
-#include <fstream>
 #include <AnKi/AnKi.h>
 
 using namespace anki;
@@ -17,19 +15,27 @@ class MyApp : public App
 {
 public:
 	Bool m_profile = false;
+	U32 m_argc = 0;
+	Char** m_argv = nullptr;
 
-	Error init(int argc, char* argv[]);
+	MyApp(U32 argc, Char** argv)
+		: App("Sandbox")
+		, m_argc(argc)
+		, m_argv(argv)
+	{
+	}
+
+	Error userPreInit() override;
+	Error userPostInit() override;
 	Error userMainLoop(Bool& quit, Second elapsedTime) override;
 };
 
-MyApp* app = nullptr;
-
-Error MyApp::init(int argc, char* argv[])
+Error MyApp::userPreInit()
 {
 #if !ANKI_OS_ANDROID
-	if(argc < 2)
+	if(m_argc < 2)
 	{
-		ANKI_LOGE("usage: %s relative/path/to/scene.lua [anki config options]", argv[0]);
+		ANKI_LOGE("usage: %s relative/path/to/scene.lua [anki config options]", m_argv[0]);
 		return Error::kUserData;
 	}
 #endif
@@ -38,12 +44,14 @@ Error MyApp::init(int argc, char* argv[])
 #if ANKI_OS_ANDROID
 	ANKI_CHECK(CVarSet::getSingleton().setFromCommandLineArguments(argc - 1, argv + 1));
 #else
-	ANKI_CHECK(CVarSet::getSingleton().setFromCommandLineArguments(argc - 2, argv + 2));
+	ANKI_CHECK(CVarSet::getSingleton().setFromCommandLineArguments(m_argc - 2, m_argv + 2));
 #endif
 
-	// Init super class
-	ANKI_CHECK(App::init());
+	return Error::kNone;
+}
 
+Error MyApp::userPostInit()
+{
 	// Other init
 	ResourceManager& resources = ResourceManager::getSingleton();
 
@@ -59,7 +67,7 @@ Error MyApp::init(int argc, char* argv[])
 #if ANKI_OS_ANDROID
 	ANKI_CHECK(resources.loadResource("Assets/Scene.lua", script));
 #else
-	ANKI_CHECK(resources.loadResource(argv[1], script));
+	ANKI_CHECK(resources.loadResource(m_argv[1], script));
 #endif
 	ANKI_CHECK(ScriptManager::getSingleton().evalString(script->getSource()));
 
@@ -410,16 +418,10 @@ Error MyApp::userMainLoop(Bool& quit, Second elapsedTime)
 ANKI_MAIN_FUNCTION(myMain)
 int myMain(int argc, char* argv[])
 {
-	Error err = Error::kNone;
-
-	app = new MyApp;
-	err = app->init(argc, argv);
-	if(!err)
-	{
-		err = app->mainLoop();
-	}
-
+	MyApp* app = new MyApp(argc, argv);
+	const Error err = app->mainLoop();
 	delete app;
+
 	if(err)
 	{
 		ANKI_LOGE("Error reported. See previous messages");

+ 6 - 5
Tests/Gr/GrAsyncCompute.cpp

@@ -13,16 +13,18 @@ using namespace anki;
 
 static void generateSphere(DynamicArray<Vec3>& positions, DynamicArray<UVec3>& indices, U32 sliceCount, U32 stackCount)
 {
+	const F32 stackCountf = F32(stackCount);
+	const F32 sliceCountf = F32(sliceCount);
 	positions.emplaceBack(0.0f, 1.0f, 0.0f);
 	const U32 v0 = 0;
 
 	// generate vertices per stack / slice
-	for(U32 i = 0u; i < stackCount - 1; i++)
+	for(F32 i = 0.0f; i < stackCountf - 1.0f; i += 1.0f)
 	{
-		const F32 phi = kPi * (i + 1) / stackCount;
-		for(F32 j = 0u; j < sliceCount; j++)
+		const F32 phi = kPi * (i + 1.0f) / stackCountf;
+		for(F32 j = 0.0f; j < sliceCountf; j += 1.0f)
 		{
-			const F32 theta = 2.0f * kPi * F32(j) / sliceCount;
+			const F32 theta = 2.0f * kPi * F32(j) / sliceCountf;
 			const F32 x = sin(phi) * cos(theta);
 			const F32 y = cos(phi);
 			const F32 z = sin(phi) * sin(theta);
@@ -66,7 +68,6 @@ static void generateSphere(DynamicArray<Vec3>& positions, DynamicArray<UVec3>& i
 ANKI_TEST(Gr, AsyncComputeBench)
 {
 	const Bool useAsyncQueue = true;
-	const Bool runConcurently = true;
 	const U32 spheresToDrawPerDimension = 100;
 	const U32 windowSize = 512;
 

+ 2 - 2
Tests/ShaderCompiler/ShaderProgramCompiler.cpp

@@ -97,7 +97,7 @@ void main()
 	HeapMemoryPool pool(allocAligned, nullptr);
 
 	const U32 threadCount = 8;
-	ThreadHive hive(threadCount, &pool);
+	ThreadHive hive(threadCount);
 
 	class TaskManager : public ShaderCompilerAsyncTaskInterface
 	{
@@ -292,7 +292,7 @@ void main()
 	HeapMemoryPool pool(allocAligned, nullptr);
 
 	const U32 threadCount = 24;
-	ThreadHive hive(threadCount, &pool);
+	ThreadHive hive(threadCount);
 
 	class TaskManager : public ShaderCompilerAsyncTaskInterface
 	{

+ 19 - 17
Tools/Image/ImageViewerMain.cpp

@@ -253,14 +253,19 @@ private:
 class MyApp : public App
 {
 public:
-	MyApp(AllocAlignedCallback allocCb, void* allocCbUserData)
-		: App(allocCb, allocCbUserData)
+	U32 m_argc = 0;
+	Char** m_argv = nullptr;
+
+	MyApp(U32 argc, Char** argv)
+		: App("ImageViewer")
+		, m_argc(argc)
+		, m_argv(argv)
 	{
 	}
 
-	Error init(int argc, char** argv, [[maybe_unused]] CString appName)
+	Error userPreInit() override
 	{
-		if(argc < 2)
+		if(m_argc < 2)
 		{
 			ANKI_LOGE("Wrong number of arguments");
 			return Error::kUserData;
@@ -268,17 +273,20 @@ public:
 
 		g_windowFullscreenCVar = 0;
 		g_dataPathsCVar = ANKI_SOURCE_DIRECTORY;
-		ANKI_CHECK(CVarSet::getSingleton().setFromCommandLineArguments(argc - 2, argv + 2));
+		ANKI_CHECK(CVarSet::getSingleton().setFromCommandLineArguments(m_argc - 2, m_argv + 2));
 
-		ANKI_CHECK(App::init());
+		return Error::kNone;
+	}
 
+	Error userPostInit() override
+	{
 		// Load the texture
 		ImageResourcePtr image;
-		ANKI_CHECK(ResourceManager::getSingleton().loadResource(argv[1], image, false));
+		ANKI_CHECK(ResourceManager::getSingleton().loadResource(m_argv[1], image, false));
 
 		// Change window name
 		String title;
-		title.sprintf("%s %u x %u Mips %u Format %s", argv[1], image->getWidth(), image->getHeight(), image->getTexture().getMipmapCount(),
+		title.sprintf("%s %u x %u Mips %u Format %s", m_argv[1], image->getWidth(), image->getHeight(), image->getTexture().getMipmapCount(),
 					  getFormatInfo(image->getTexture().getFormat()).m_name);
 		NativeWindow::getSingleton().setWindowTitle(title);
 
@@ -304,16 +312,10 @@ public:
 ANKI_MAIN_FUNCTION(myMain)
 int myMain(int argc, char* argv[])
 {
-	Error err = Error::kNone;
-
-	MyApp* app = new MyApp(allocAligned, nullptr);
-	err = app->init(argc, argv, "Texture Viewer");
-	if(!err)
-	{
-		err = app->mainLoop();
-	}
-
+	MyApp* app = new MyApp(argc, argv);
+	const Error err = app->mainLoop();
 	delete app;
+
 	if(err)
 	{
 		ANKI_LOGE("Error reported. Bye!!");