From 75777e9996df172c24be5b6734ed57cb81502159 Mon Sep 17 00:00:00 2001
From: Tobias Frisch <tfrisch@uni-koblenz.de>
Date: Tue, 30 Aug 2022 13:39:19 +0200
Subject: [PATCH] Abstracted main loop for applications

Signed-off-by: Tobias Frisch <tfrisch@uni-koblenz.de>
---
 include/vkcv/Core.hpp                       |  9 ++++
 include/vkcv/EventFunctionTypes.hpp         |  7 +++
 projects/bindless_textures/src/main.cpp     | 28 +++---------
 projects/first_mesh/src/main.cpp            | 26 +++--------
 projects/first_scene/src/main.cpp           | 29 +++---------
 projects/first_triangle/src/main.cpp        | 41 ++++++-----------
 projects/head_demo/src/main.cpp             | 29 +++---------
 projects/indirect_draw/src/main.cpp         | 32 +++-----------
 projects/mesh_shader/src/main.cpp           | 31 +++----------
 projects/particle_simulation/src/main.cpp   | 24 +++-------
 projects/path_tracer/src/main.cpp           | 30 +++----------
 projects/rtx_ambient_occlusion/src/main.cpp | 27 +++---------
 projects/saf_r/src/main.cpp                 | 39 ++++------------
 projects/sph/src/main.cpp                   | 49 ++++++++-------------
 projects/voxelization/src/main.cpp          | 29 +++---------
 projects/wobble_bobble/src/main.cpp         | 31 +++----------
 src/vkcv/Core.cpp                           | 31 +++++++++++++
 src/vkcv/WindowManager.cpp                  | 12 +++++
 src/vkcv/WindowManager.hpp                  |  9 ++++
 19 files changed, 174 insertions(+), 339 deletions(-)

diff --git a/include/vkcv/Core.hpp b/include/vkcv/Core.hpp
index 5fb7ac78..593c28de 100644
--- a/include/vkcv/Core.hpp
+++ b/include/vkcv/Core.hpp
@@ -854,5 +854,14 @@ namespace vkcv
 		 */
 		void setDebugLabel(const CommandStreamHandle &handle, const std::string &label);
 		
+		/**
+		 * @brief Runs the application in the current until all windows get closed.
+		 *
+		 * The frame callback will be called for each window every single frame.
+		 *
+		 * @param[in] frame Frame callback
+		 */
+		void run(const WindowFrameFunction &frame);
+		
     };
 }
diff --git a/include/vkcv/EventFunctionTypes.hpp b/include/vkcv/EventFunctionTypes.hpp
index cf7763ea..5bfb29d6 100644
--- a/include/vkcv/EventFunctionTypes.hpp
+++ b/include/vkcv/EventFunctionTypes.hpp
@@ -8,6 +8,7 @@
 #include <vulkan/vulkan.hpp>
 
 #include "Event.hpp"
+#include "Handles.hpp"
 
 namespace vkcv {
 	
@@ -21,4 +22,10 @@ namespace vkcv {
 	 */
 	typedef typename event_function<>::type FinishCommandFunction;
 	
+	/**
+	 * @brief Function to be called each frame for every open window.
+	 */
+	typedef typename event_function<const WindowHandle&, double, double, uint32_t, uint32_t>::type 
+	        WindowFrameFunction;
+	
 }
\ No newline at end of file
diff --git a/projects/bindless_textures/src/main.cpp b/projects/bindless_textures/src/main.cpp
index 8f594e59..72e34a42 100644
--- a/projects/bindless_textures/src/main.cpp
+++ b/projects/bindless_textures/src/main.cpp
@@ -218,20 +218,9 @@ int main(int argc, const char** argv) {
 	cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
 	
 	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -3));
-
-    auto start = std::chrono::system_clock::now();
-    
-	while (vkcv::Window::hasOpenWindow()) {
-        vkcv::Window::pollEvents();
-		
-		if (window.getHeight() == 0 || window.getWidth() == 0)
-			continue;
-		
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
-			continue;
-		}
-		
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if ((!depthBuffer) ||
 			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
 			(swapchainHeight != core.getImageHeight(depthBuffer))) {
@@ -241,12 +230,8 @@ int main(int argc, const char** argv) {
 					swapchainHeight
 			).getHandle();
 		}
-  
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-		
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+
+		cameraManager.update(dt);
         glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 
 		vkcv::PushConstants pushConstants = vkcv::pushConstants<glm::mat4>();
@@ -266,8 +251,7 @@ int main(int argc, const char** argv) {
 		
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-		core.endFrame(windowHandle);
-	}
+	});
 	
 	return 0;
 }
diff --git a/projects/first_mesh/src/main.cpp b/projects/first_mesh/src/main.cpp
index 27e0630e..73ef3bc1 100644
--- a/projects/first_mesh/src/main.cpp
+++ b/projects/first_mesh/src/main.cpp
@@ -154,20 +154,9 @@ int main(int argc, const char** argv) {
     uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
 	
 	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -3));
-
-    auto start = std::chrono::system_clock::now();
-    
-	while (vkcv::Window::hasOpenWindow()) {
-        vkcv::Window::pollEvents();
-		
-		if (window.getHeight() == 0 || window.getWidth() == 0)
-			continue;
-		
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
-			continue;
-		}
-		
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if ((!depthBuffer) ||
 			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
 			(swapchainHeight != core.getImageHeight(depthBuffer))) {
@@ -177,12 +166,8 @@ int main(int argc, const char** argv) {
 					swapchainHeight
 			).getHandle();
 		}
-  
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
 		
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		cameraManager.update(dt);
         glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 
 		vkcv::PushConstants pushConstants = vkcv::pushConstants<glm::mat4>();
@@ -202,8 +187,7 @@ int main(int argc, const char** argv) {
 		
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-		core.endFrame(windowHandle);
-	}
+	});
 	
 	return 0;
 }
diff --git a/projects/first_scene/src/main.cpp b/projects/first_scene/src/main.cpp
index 17b41378..2dbafc78 100644
--- a/projects/first_scene/src/main.cpp
+++ b/projects/first_scene/src/main.cpp
@@ -96,18 +96,8 @@ int main(int argc, const char** argv) {
 
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 	
-	auto start = std::chrono::system_clock::now();
-	while (vkcv::Window::hasOpenWindow()) {
-        vkcv::Window::pollEvents();
-		
-		if(window.getHeight() == 0 || window.getWidth() == 0)
-			continue;
-		
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight,windowHandle)) {
-			continue;
-		}
-		
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if ((!depthBuffer) ||
 			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
 			(swapchainHeight != core.getImageHeight(depthBuffer))) {
@@ -117,12 +107,8 @@ int main(int argc, const char** argv) {
 					swapchainHeight
 			).getHandle();
 		}
-  
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
 		
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		cameraManager.update(dt);
 
 		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
@@ -145,19 +131,14 @@ int main(int argc, const char** argv) {
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
 
-        auto stop = std::chrono::system_clock::now();
-        auto kektime = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
-
         gui.beginGUI();
 
         ImGui::Begin("Settings");
-        ImGui::Text("Deltatime %fms, %f", 0.001 * static_cast<double>(kektime.count()), 1/(0.000001 * static_cast<double>(kektime.count())));
+        ImGui::Text("Deltatime %fms, %f", dt * 1000, 1/dt);
         ImGui::End();
 
         gui.endGUI();
-
-		core.endFrame(windowHandle);
-	}
+	});
 	
 	return 0;
 }
diff --git a/projects/first_triangle/src/main.cpp b/projects/first_triangle/src/main.cpp
index 5ad87a57..aef3ffde 100644
--- a/projects/first_triangle/src/main.cpp
+++ b/projects/first_triangle/src/main.cpp
@@ -67,8 +67,6 @@ int main(int argc, const char** argv) {
 	}
 	
 	core.setDebugLabel(trianglePipeline, "Triangle Pipeline");
-	
-	auto start = std::chrono::system_clock::now();
 
 	const vkcv::Mesh renderMesh({}, triangleIndexBuffer.getVulkanHandle(), 3);
 	vkcv::DrawcallInfo drawcall(renderMesh, {},1);
@@ -83,38 +81,27 @@ int main(int argc, const char** argv) {
     cameraManager.getCamera(camIndex1).setPosition(glm::vec3(0.0f, 0.0f, 0.0f));
     cameraManager.getCamera(camIndex1).setCenter(glm::vec3(0.0f, 0.0f, -1.0f));
 
-	while (vkcv::Window::hasOpenWindow())
-	{
-        vkcv::Window::pollEvents();
-
-		uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem
-		if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
-			continue;
-		}
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+			uint32_t swapchainWidth, uint32_t swapchainHeight) {
+		cameraManager.update(dt);
 		
-        auto end = std::chrono::system_clock::now();
-        auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-        start = end;
-		
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
-        glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
+		glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 		
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
 		core.setDebugLabel(cmdStream, "Render Commands");
-
+		
 		core.recordDrawcallsToCmdStream(
-			cmdStream,
-			trianglePipeline,
-			vkcv::pushConstants<glm::mat4>(mvp),
-			{ drawcall },
-			{ swapchainInput },
-			windowHandle
+				cmdStream,
+				trianglePipeline,
+				vkcv::pushConstants<glm::mat4>(mvp),
+				{ drawcall },
+				{ swapchainInput },
+				windowHandle
 		);
-
+		
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-	    
-	    core.endFrame(windowHandle);
-	}
+	});
+	
 	return 0;
 }
diff --git a/projects/head_demo/src/main.cpp b/projects/head_demo/src/main.cpp
index 905c333f..e7801286 100644
--- a/projects/head_demo/src/main.cpp
+++ b/projects/head_demo/src/main.cpp
@@ -161,18 +161,8 @@ int main(int argc, const char** argv) {
 	vkcv::effects::BloomAndFlaresEffect bloomAndFlares (core);
 	vkcv::upscaling::FSRUpscaling upscaling (core);
 	
-	auto start = std::chrono::system_clock::now();
-	while (vkcv::Window::hasOpenWindow()) {
-		vkcv::Window::pollEvents();
-		
-		if(window.getHeight() == 0 || window.getWidth() == 0)
-			continue;
-		
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight,windowHandle)) {
-			continue;
-		}
-		
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if ((swapchainWidth != swapchainExtent.width) || ((swapchainHeight != swapchainExtent.height))) {
 			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
 			
@@ -186,12 +176,8 @@ int main(int argc, const char** argv) {
 			swapchainExtent.width = swapchainWidth;
 			swapchainExtent.height = swapchainHeight;
 		}
-		
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-		
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+
+		cameraManager.update(dt);
 		
 		clipBuffer.fill({ clipLimit, -clipX, -clipY, -clipZ });
 		
@@ -234,9 +220,6 @@ int main(int argc, const char** argv) {
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
 		
-		auto stop = std::chrono::system_clock::now();
-		auto kektime = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
-		
 		gui.beginGUI();
 		
 		ImGui::Begin("Settings");
@@ -247,9 +230,7 @@ int main(int argc, const char** argv) {
 		ImGui::End();
 		
 		gui.endGUI();
-		
-		core.endFrame(windowHandle);
-	}
+	});
 	
 	return 0;
 }
diff --git a/projects/indirect_draw/src/main.cpp b/projects/indirect_draw/src/main.cpp
index 733469fd..5631d3c1 100644
--- a/projects/indirect_draw/src/main.cpp
+++ b/projects/indirect_draw/src/main.cpp
@@ -512,8 +512,6 @@ int main(int argc, const char** argv) {
 	
     const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
-    auto start = std::chrono::system_clock::now();
-
     float ceiledDispatchCount = static_cast<float>(indexedIndirectCommands.size()) / 64.0f;
     ceiledDispatchCount = std::ceil(ceiledDispatchCount);
     const vkcv::DispatchSize dispatchCount = static_cast<uint32_t>(ceiledDispatchCount);
@@ -522,29 +520,17 @@ int main(int argc, const char** argv) {
     vkcv::PushConstants emptyPushConstant(0);
 
     bool updateFrustumPlanes    = true;
-
-    while (vkcv::Window::hasOpenWindow()) {
-        vkcv::Window::pollEvents();
-		
-		if (window.getHeight() == 0 || window.getWidth() == 0)
-			continue;
-		
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
-			continue;
-		}
-		
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if ((!depthBuffer) ||
 			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
 			(swapchainHeight != core.getImageHeight(depthBuffer))) {
 			depthBuffer = core.createImage(vk::Format::eD32Sfloat, swapchainWidth, swapchainHeight).getHandle();
 		}
   
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+		cameraManager.update(dt);
 		
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
         vkcv::camera::Camera cam = cameraManager.getActiveCamera();
 		vkcv::PushConstants pushConstants = vkcv::pushConstants<glm::mat4>();
 		pushConstants.appendDrawcall(cam.getProjection() * cam.getView());
@@ -581,21 +567,17 @@ int main(int argc, const char** argv) {
 
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-
-		auto stop = std::chrono::system_clock::now();
-		auto kektime = std::chrono::duration_cast<std::chrono::microseconds>(stop - start);
+		
         gui.beginGUI();
 
         ImGui::Begin("Settings");
         ImGui::Checkbox("Update frustum culling", &updateFrustumPlanes);
-		ImGui::Text("Deltatime %fms, %f", 0.001 * static_cast<double>(kektime.count()), 1/(0.000001 * static_cast<double>(kektime.count())));
+		ImGui::Text("Deltatime %fms, %f", dt * 1000, 1/dt);
 
         ImGui::End();
 
         gui.endGUI();
-
-		core.endFrame(windowHandle);
-	}
+	});
 	
 	return 0;
 }
diff --git a/projects/mesh_shader/src/main.cpp b/projects/mesh_shader/src/main.cpp
index f80c47ff..07f9b5fc 100644
--- a/projects/mesh_shader/src/main.cpp
+++ b/projects/mesh_shader/src/main.cpp
@@ -280,9 +280,6 @@ int main(int argc, const char** argv) {
     core.writeDescriptorSet( meshShaderDescriptorSet, meshShaderWrites);
 
     vkcv::ImageHandle depthBuffer;
-
-    auto start = std::chrono::system_clock::now();
-
 	vkcv::ImageHandle swapchainImageHandle = vkcv::ImageHandle::createSwapchainImageHandle();
 
     const vkcv::Mesh renderMesh(vertexBufferBindings, indexBuffer.getVulkanHandle(), mesh.vertexGroups[0].numIndices, vkcv::IndexBitCount::Bit32);
@@ -296,16 +293,9 @@ int main(int argc, const char** argv) {
 
 	bool useMeshShader          = true;
 	bool updateFrustumPlanes    = true;
-
-	while (vkcv::Window::hasOpenWindow())
-	{
-		vkcv::Window::pollEvents();
-
-		uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem
-		if (!core.beginFrame(swapchainWidth, swapchainHeight,windowHandle)) {
-			continue;
-		}
-		
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if ((!depthBuffer) ||
 			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
 			(swapchainHeight != core.getImageHeight(depthBuffer))) {
@@ -316,11 +306,7 @@ int main(int argc, const char** argv) {
 			).getHandle();
 		}
 		
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-		start = end;
-		
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		cameraManager.update(dt);
 
 		const vkcv::camera::Camera& camera = cameraManager.getActiveCamera();
 
@@ -360,9 +346,7 @@ int main(int argc, const char** argv) {
 				{ renderTargets },
 				windowHandle
 			);
-		}
-		else {
-
+		} else {
 			vkcv::DescriptorSetUsage descriptorUsage(0, vertexShaderDescriptorSet);
 
 			core.recordDrawcallsToCmdStream(
@@ -386,8 +370,7 @@ int main(int argc, const char** argv) {
 
 		ImGui::End();
 		gui.endGUI();
-
-		core.endFrame(windowHandle);
-	}
+	});
+	
 	return 0;
 }
diff --git a/projects/particle_simulation/src/main.cpp b/projects/particle_simulation/src/main.cpp
index 5ade2185..ba62b126 100644
--- a/projects/particle_simulation/src/main.cpp
+++ b/projects/particle_simulation/src/main.cpp
@@ -188,9 +188,7 @@ int main(int argc, const char **argv) {
     std::vector<glm::mat4> modelMatrices;
     std::vector<vkcv::DrawcallInfo> drawcalls;
     drawcalls.push_back(vkcv::DrawcallInfo(renderMesh, {descriptorUsage}, particleSystem.getParticles().size()));
-
-    auto start = std::chrono::system_clock::now();
-
+	
     glm::vec4 colorData = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
     uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
     uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
@@ -230,14 +228,9 @@ int main(int argc, const char **argv) {
 
     std::uniform_real_distribution<float> rdm = std::uniform_real_distribution<float>(0.95f, 1.05f);
     std::default_random_engine rdmEngine;
-    while (vkcv::Window::hasOpenWindow()) {
-        vkcv::Window::pollEvents();
-
-        uint32_t swapchainWidth, swapchainHeight;
-        if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
-            continue;
-        }
 	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if ((core.getImageWidth(colorBuffer) != swapchainWidth) ||
 			(core.getImageHeight(colorBuffer) != swapchainHeight)) {
 			colorBuffer = core.createImage(
@@ -251,11 +244,7 @@ int main(int argc, const char **argv) {
         color.fill(&colorData);
         position.fill(&pos);
 
-        auto end = std::chrono::system_clock::now();
-        float deltatime = 0.000001 * static_cast<float>( std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() );
-        start = end;
-
-        cameraManager.update(deltatime);
+        cameraManager.update(dt);
 
         // split view and projection to allow for easy billboarding in shader
         struct {
@@ -268,7 +257,7 @@ int main(int argc, const char **argv) {
 
         auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
         float random = rdm(rdmEngine);
-        glm::vec2 pushData = glm::vec2(deltatime, random);
+        glm::vec2 pushData = glm::vec2(dt, random);
 
         vkcv::PushConstants pushConstantsCompute = vkcv::pushConstants<glm::vec2>();
         pushConstantsCompute.appendDrawcall(pushData);
@@ -323,8 +312,7 @@ int main(int argc, const char **argv) {
 
         core.prepareSwapchainImageForPresent(cmdStream);
         core.submitCommandStream(cmdStream);
-        core.endFrame(windowHandle);
-    }
+    });
 
     return 0;
 }
diff --git a/projects/path_tracer/src/main.cpp b/projects/path_tracer/src/main.cpp
index 6c34f51a..6b0133b7 100644
--- a/projects/path_tracer/src/main.cpp
+++ b/projects/path_tracer/src/main.cpp
@@ -220,9 +220,7 @@ int main(int argc, const char** argv) {
 	uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
 
 	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, -2));
-
-	auto    startTime       = std::chrono::system_clock::now();
-	float   time            = 0;
+	
 	int     frameIndex      = 0;
 	bool    clearMeanImage  = true;
 	bool    updateMaterials = true;
@@ -245,16 +243,9 @@ int main(int argc, const char** argv) {
 
 	glm::vec3   skyColor            = glm::vec3(0.2, 0.7, 0.8);
 	float       skyColorMultiplier  = 1;
-
-	while (vkcv::Window::hasOpenWindow())
-	{
-		vkcv::Window::pollEvents();
-
-		uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem
-		if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
-			continue;
-		}
-
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if (swapchainWidth != widthPrevious || swapchainHeight != heightPrevious) {
 
 			// resize images
@@ -297,13 +288,7 @@ int main(int argc, const char** argv) {
 			clearMeanImage = true;
 		}
 
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - startTime);
-		startTime = end;
-
-		time += 0.000001f * static_cast<float>(deltatime.count());
-
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		cameraManager.update(dt);
 
 		const vkcv::CommandStreamHandle cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
 
@@ -454,9 +439,8 @@ int main(int argc, const char** argv) {
 			gui.endGUI();
 		}
 
-		core.endFrame(windowHandle);
-
 		frameIndex++;
-	}
+	});
+
 	return 0;
 }
diff --git a/projects/rtx_ambient_occlusion/src/main.cpp b/projects/rtx_ambient_occlusion/src/main.cpp
index 4ce5b2b5..0f4f98f7 100644
--- a/projects/rtx_ambient_occlusion/src/main.cpp
+++ b/projects/rtx_ambient_occlusion/src/main.cpp
@@ -93,19 +93,9 @@ int main(int argc, const char** argv) {
 	const vkcv::ImageHandle swapchainInput = vkcv::ImageHandle::createSwapchainImageHandle();
 
 	vkcv::DescriptorWrites rtxWrites;
-
-	auto start = std::chrono::system_clock::now();
-	while (vkcv::Window::hasOpenWindow()) {
-        vkcv::Window::pollEvents();
-
-		if(core.getWindow(windowHandle).getHeight() == 0 || core.getWindow(windowHandle).getWidth() == 0)
-			continue;
-
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight,windowHandle)) {
-			continue;
-		}
-
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if ((!depthBuffer) ||
 			(swapchainWidth != core.getImageWidth(depthBuffer)) ||
 			((swapchainHeight != core.getImageHeight(depthBuffer)))) {
@@ -115,12 +105,8 @@ int main(int argc, const char** argv) {
 					swapchainHeight
 			).getHandle();
 		}
-
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		
+		cameraManager.update(dt);
 
 		const std::vector<vkcv::ImageHandle> renderTargets = { swapchainInput, depthBuffer };
 		
@@ -154,8 +140,7 @@ int main(int argc, const char** argv) {
 
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-		core.endFrame(windowHandle);
-	}
+	});
 
 	return 0;
 }
diff --git a/projects/saf_r/src/main.cpp b/projects/saf_r/src/main.cpp
index d514eb2f..16149c1b 100644
--- a/projects/saf_r/src/main.cpp
+++ b/projects/saf_r/src/main.cpp
@@ -113,15 +113,13 @@ int main(int argc, const char** argv) {
 	lights.push_back(safrScene::Light(glm::vec3(30,  20,  30), 1.7));
     */
     createQuadraticLightCluster(lights, 10, 2.5f, 20, 1.5f);
-
-
+	
 	vkcv::SamplerHandle sampler = core.createSampler(
 		vkcv::SamplerFilterType::LINEAR,
 		vkcv::SamplerFilterType::LINEAR,
 		vkcv::SamplerMipmapMode::LINEAR,
 		vkcv::SamplerAddressMode::REPEAT
 	);
-
 	
 	//create Buffer for compute shader
 	vkcv::Buffer<safrScene::Light> lightsBuffer = vkcv::buffer<safrScene::Light>(
@@ -185,8 +183,6 @@ int main(int argc, const char** argv) {
 		return EXIT_FAILURE;
 	}
 	
-	auto start = std::chrono::system_clock::now();
-
 	const vkcv::Mesh renderMesh({}, safrIndexBuffer.getVulkanHandle(), 3);
 	vkcv::DescriptorSetUsage descriptorUsage(0, descriptorSet);
 	vkcv::DrawcallInfo drawcall(renderMesh, { descriptorUsage }, 1);
@@ -199,25 +195,9 @@ int main(int argc, const char** argv) {
 	cameraManager.getCamera(camIndex0).setPosition(glm::vec3(0, 0, 2));
 	cameraManager.getCamera(camIndex1).setPosition(glm::vec3(0.0f, 0.0f, 0.0f));
 	cameraManager.getCamera(camIndex1).setCenter(glm::vec3(0.0f, 0.0f, -1.0f));
-
-	float time = 0;
 	
-	while (vkcv::Window::hasOpenWindow())
-	{
-		vkcv::Window::pollEvents();
-
-		uint32_t swapchainWidth, swapchainHeight; // No resizing = No problem
-		if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
-			continue;
-		}
-
-		//configure timer
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-		start = end;
-		
-		time += 0.000001f * static_cast<float>(deltatime.count());
-		
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		//adjust light position
 		/*
 		639a53157e7d3936caf7c3e40379159cbcf4c89e
@@ -227,13 +207,13 @@ int main(int argc, const char** argv) {
 		lightsBuffer.fill(lights);
 		*/
 
-		spheres[0].center.y += std::cos(time * 0.5f * 3.141f) * 0.25f;
-		spheres[1].center.x += std::cos(time * 2.f) * 0.25f;
-		spheres[1].center.z += std::cos(time * 2.f + 0.5f * 3.141f) * 0.25f;
+		spheres[0].center.y += std::cos(t * 0.5f * 3.141f) * 0.25f;
+		spheres[1].center.x += std::cos(t * 2.f) * 0.25f;
+		spheres[1].center.z += std::cos(t * 2.f + 0.5f * 3.141f) * 0.25f;
         sphereBuffer.fill(spheres);
 
 		//update camera
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		cameraManager.update(dt);
 		glm::mat4 mvp = cameraManager.getActiveCamera().getMVP();
 		glm::mat4 proj = cameraManager.getActiveCamera().getProjection();
 
@@ -280,8 +260,7 @@ int main(int argc, const char** argv) {
 
 		core.prepareSwapchainImageForPresent(cmdStream);
 		core.submitCommandStream(cmdStream);
-
-		core.endFrame(windowHandle);
-	}
+	});
+	
 	return 0;
 }
diff --git a/projects/sph/src/main.cpp b/projects/sph/src/main.cpp
index ab02c4a0..ec04f364 100644
--- a/projects/sph/src/main.cpp
+++ b/projects/sph/src/main.cpp
@@ -205,9 +205,7 @@ int main(int argc, const char **argv) {
 
     std::vector<vkcv::DrawcallInfo> drawcalls;
     drawcalls.push_back(vkcv::DrawcallInfo(renderMesh, {descriptorUsage}, numberParticles));
-
-    auto start = std::chrono::system_clock::now();
-
+	
     glm::vec4 colorData = glm::vec4(1.0f, 1.0f, 0.0f, 1.0f);
     uint32_t camIndex0 = cameraManager.addCamera(vkcv::camera::ControllerType::PILOT);
     uint32_t camIndex1 = cameraManager.addCamera(vkcv::camera::ControllerType::TRACKBALL);
@@ -241,15 +239,9 @@ int main(int argc, const char **argv) {
 			"shaders/tonemapping.comp",
 			tonemappingPipe
 	);
-
-    while (vkcv::Window::hasOpenWindow()) {
-        vkcv::Window::pollEvents();
-
-        uint32_t swapchainWidth, swapchainHeight;
-        if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
-            continue;
-        }
-		
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if ((core.getImageWidth(colorBuffer) != swapchainWidth) ||
 			(core.getImageHeight(colorBuffer) != swapchainHeight)) {
 			colorBuffer = core.createImage(
@@ -263,11 +255,7 @@ int main(int argc, const char **argv) {
         color.fill(&colorData);
         position.fill(&pos);
 
-        auto end = std::chrono::system_clock::now();
-        float deltatime = 0.000001 * static_cast<float>( std::chrono::duration_cast<std::chrono::microseconds>(end - start).count() );
-        start = end;
-
-        cameraManager.update(deltatime);
+        cameraManager.update(dt);
 
         // split view and projection to allow for easy billboarding in shader
         struct {
@@ -285,35 +273,35 @@ int main(int argc, const char **argv) {
 
         // keybindings rotation
         if (glfwGetKey(window.getWindow(), GLFW_KEY_LEFT) == GLFW_PRESS)
-            rotationx += deltatime * 50;
+            rotationx += dt * 50;
         if (glfwGetKey(window.getWindow(), GLFW_KEY_RIGHT) == GLFW_PRESS)
-            rotationx -= deltatime * 50;
+            rotationx -= dt * 50;
         
         if (glfwGetKey(window.getWindow(), GLFW_KEY_UP) == GLFW_PRESS)
-            rotationy += deltatime * 50;
+            rotationy += dt * 50;
         if (glfwGetKey(window.getWindow(), GLFW_KEY_DOWN) == GLFW_PRESS)
-            rotationy -= deltatime * 50;
+            rotationy -= dt * 50;
 
         // keybindings params
         if (glfwGetKey(window.getWindow(), GLFW_KEY_T) == GLFW_PRESS)
-            param_h += deltatime * 0.2;
+            param_h += dt * 0.2;
         if (glfwGetKey(window.getWindow(), GLFW_KEY_G) == GLFW_PRESS)
-            param_h -= deltatime * 0.2;
+            param_h -= dt * 0.2;
 
         if (glfwGetKey(window.getWindow(), GLFW_KEY_Y) == GLFW_PRESS)
-            param_mass += deltatime * 0.2;
+            param_mass += dt * 0.2;
         if (glfwGetKey(window.getWindow(), GLFW_KEY_H) == GLFW_PRESS)
-            param_mass -= deltatime * 0.2;
+            param_mass -= dt * 0.2;
 
         if (glfwGetKey(window.getWindow(), GLFW_KEY_U) == GLFW_PRESS)
-            param_gasConstant += deltatime * 1500.0;
+            param_gasConstant += dt * 1500.0;
         if (glfwGetKey(window.getWindow(), GLFW_KEY_J) == GLFW_PRESS)
-            param_gasConstant -= deltatime * 1500.0;
+            param_gasConstant -= dt * 1500.0;
 
         if (glfwGetKey(window.getWindow(), GLFW_KEY_I) == GLFW_PRESS)
-            param_offset += deltatime * 400.0;
+            param_offset += dt * 400.0;
         if (glfwGetKey(window.getWindow(), GLFW_KEY_K) == GLFW_PRESS)
-            param_offset -= deltatime * 400.0;
+            param_offset -= dt * 400.0;
 
         if (glfwGetKey(window.getWindow(), GLFW_KEY_O) == GLFW_PRESS)
             param_viscosity = 50;
@@ -427,8 +415,7 @@ int main(int argc, const char **argv) {
 
         core.prepareSwapchainImageForPresent(cmdStream);
         core.submitCommandStream(cmdStream);
-        core.endFrame(windowHandle);
-    }
+    });
 
     return 0;
 }
diff --git a/projects/voxelization/src/main.cpp b/projects/voxelization/src/main.cpp
index 8f568407..61e7d35b 100644
--- a/projects/voxelization/src/main.cpp
+++ b/projects/voxelization/src/main.cpp
@@ -643,17 +643,9 @@ int main(int argc, const char** argv) {
 	glm::vec3   absorptionColor     = glm::vec3(1);
 	float       absorptionDensity   = 0.005;
 	float       volumetricAmbient   = 0.2;
-
-	auto start = std::chrono::system_clock::now();
-	const auto appStartTime = start;
-	while (vkcv::Window::hasOpenWindow()) {
-		vkcv::Window::pollEvents();
-
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
-			continue;
-		}
-		
+	
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		uint32_t width, height;
 		vkcv::upscaling::getFSRResolution(
 				fsrMode,
@@ -717,9 +709,6 @@ int main(int argc, const char** argv) {
 			).getHandle();
 		}
 
-		auto end = std::chrono::system_clock::now();
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
-
 		// update descriptor sets which use swapchain image
 		vkcv::DescriptorWrites tonemappingDescriptorWrites;
 		tonemappingDescriptorWrites.writeSampledImage(0, resolvedColorBuffer);
@@ -743,8 +732,7 @@ int main(int argc, const char** argv) {
 		resolveDescriptorWrites.writeStorageImage(2, resolvedColorBuffer);
 		core.writeDescriptorSet(resolveDescriptorSet, resolveDescriptorWrites);
 
-		start = end;
-		cameraManager.update(0.000001 * static_cast<double>(deltatime.count()));
+		cameraManager.update(dt);
 		cameraPosBuffer.fill({ cameraManager.getActiveCamera().getPosition() });
 
 		auto cmdStream = core.createCommandStream(vkcv::QueueType::Graphics);
@@ -914,11 +902,8 @@ int main(int argc, const char** argv) {
 		core.prepareImageForStorage(cmdStream, swapchainInput);
 		core.prepareImageForSampling(cmdStream, swapBuffer2);
 		
-		auto timeSinceStart = std::chrono::duration_cast<std::chrono::microseconds>(end - appStartTime);
-		float timeF         = static_cast<float>(timeSinceStart.count()) * 0.01f;
-		
 		vkcv::PushConstants timePushConstants = vkcv::pushConstants<float>();
-		timePushConstants.appendDrawcall(timeF);
+		timePushConstants.appendDrawcall(static_cast<float>(t * 100000.0));
 		
 		fullscreenDispatchCount = vkcv::dispatchInvocations(
 				vkcv::DispatchSize(swapchainWidth, swapchainHeight),
@@ -1022,9 +1007,7 @@ int main(int argc, const char** argv) {
 		}
 
 		gui.endGUI();
-
-		core.endFrame(windowHandle);
-	}
+	});
 	
 	return 0;
 }
diff --git a/projects/wobble_bobble/src/main.cpp b/projects/wobble_bobble/src/main.cpp
index da02ed38..683b99b1 100644
--- a/projects/wobble_bobble/src/main.cpp
+++ b/projects/wobble_bobble/src/main.cpp
@@ -621,20 +621,8 @@ int main(int argc, const char **argv) {
 	
 	float speed_factor = 1.0f;
 	
-	auto start = std::chrono::system_clock::now();
-	auto current = start;
-	
-	while (vkcv::Window::hasOpenWindow()) {
-		vkcv::Window::pollEvents();
-		
-		if (window.getHeight() == 0 || window.getWidth() == 0)
-			continue;
-		
-		uint32_t swapchainWidth, swapchainHeight;
-		if (!core.beginFrame(swapchainWidth, swapchainHeight, windowHandle)) {
-			continue;
-		}
-		
+	core.run([&](const vkcv::WindowHandle &windowHandle, double t, double dt,
+				 uint32_t swapchainWidth, uint32_t swapchainHeight) {
 		if ((swapchainWidth != swapchainExtent.width) || ((swapchainHeight != swapchainExtent.height))) {
 			depthBuffer = core.createImage(
 					vk::Format::eD32Sfloat,
@@ -646,16 +634,9 @@ int main(int argc, const char **argv) {
 			swapchainExtent.height = swapchainHeight;
 		}
 		
-		auto next = std::chrono::system_clock::now();
-		
-		auto time = std::chrono::duration_cast<std::chrono::microseconds>(next - start);
-		auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(next - current);
-		
-		current = next;
-		
 		Physics physics;
-		physics.t = static_cast<float>(0.000001 * static_cast<double>(time.count()));
-		physics.dt = static_cast<float>(0.000001 * static_cast<double>(deltatime.count()));
+		physics.t = static_cast<float>(t);
+		physics.dt = static_cast<float>(dt);
 		physics.speedfactor = speed_factor;
 		
 		vkcv::PushConstants physicsPushConstants = vkcv::pushConstants<Physics>();
@@ -871,9 +852,7 @@ int main(int argc, const char **argv) {
 		
 		ImGui::End();
 		gui.endGUI();
-		
-		core.endFrame(windowHandle);
-	}
+	});
 	
 	simulation.unmap();
 	return 0;
diff --git a/src/vkcv/Core.cpp b/src/vkcv/Core.cpp
index 2382aff1..2cc1ac1b 100644
--- a/src/vkcv/Core.cpp
+++ b/src/vkcv/Core.cpp
@@ -1425,4 +1425,35 @@ namespace vkcv
 				label
 		);
 	}
+	
+	void Core::run(const vkcv::WindowFrameFunction &frame) {
+		auto start = std::chrono::system_clock::now();
+		double t = 0.0;
+		
+		if (!frame)
+			return;
+		
+		while (Window::hasOpenWindow()) {
+			vkcv::Window::pollEvents();
+			
+			auto end = std::chrono::system_clock::now();
+			auto deltatime = std::chrono::duration_cast<std::chrono::microseconds>(end - start);
+			start = end;
+			
+			double dt = 0.000001 * static_cast<double>(deltatime.count());
+			
+			for (const auto &window : m_WindowManager->getWindowHandles()) {
+				uint32_t swapchainWidth, swapchainHeight;
+				if (!beginFrame(swapchainWidth, swapchainHeight, window)) {
+					continue;
+				}
+				
+				frame(window, t, dt, swapchainWidth, swapchainHeight);
+				endFrame(window);
+			}
+			
+			t += dt;
+		}
+	}
+	
 }
diff --git a/src/vkcv/WindowManager.cpp b/src/vkcv/WindowManager.cpp
index 4a367bd7..930c5f3a 100644
--- a/src/vkcv/WindowManager.cpp
+++ b/src/vkcv/WindowManager.cpp
@@ -54,5 +54,17 @@ namespace vkcv {
 	Window &WindowManager::getWindow(const WindowHandle& handle) const {
 		return *(*this)[handle];
 	}
+	
+	std::vector<WindowHandle> WindowManager::getWindowHandles() const {
+		std::vector<WindowHandle> handles;
+		
+		for (size_t id = 0; id < getCount(); id++) {
+			if (getById(id)->isOpen()) {
+				handles.push_back(WindowHandle(id));
+			}
+		}
+		
+		return handles;
+	}
 
 }
\ No newline at end of file
diff --git a/src/vkcv/WindowManager.hpp b/src/vkcv/WindowManager.hpp
index 4ffa4cc5..172cec2a 100644
--- a/src/vkcv/WindowManager.hpp
+++ b/src/vkcv/WindowManager.hpp
@@ -60,6 +60,15 @@ namespace vkcv {
 		 */
 		[[nodiscard]]
 		Window &getWindow(const WindowHandle& handle) const;
+		
+		/**
+		 * Returns a list of window handles for current active
+		 * and open windows.
+		 *
+		 * @return List of window handles
+		 */
+		[[nodiscard]]
+		std::vector<WindowHandle> getWindowHandles() const;
 
 	};
 	
-- 
GitLab