diff --git a/external/Vulkan/examples/renderheadless/renderheadless.cpp b/external/Vulkan/examples/renderheadless/renderheadless.cpp
index aeaffd20df51c2dce021d643d09c028117b0a148..912d07c5d06d9c7199a5b6970bd6a0092c3076ba 100644
--- a/external/Vulkan/examples/renderheadless/renderheadless.cpp
+++ b/external/Vulkan/examples/renderheadless/renderheadless.cpp
@@ -6,6 +6,8 @@
 * This code is licensed under the MIT license (MIT) (http://opensource.org/licenses/MIT)
 */
 
+#include "renderheadless.h"
+
 #if defined(_WIN32)
 #pragma comment(linker, "/subsystem:console")
 #elif defined(VK_USE_PLATFORM_ANDROID_KHR)
@@ -93,6 +95,17 @@ public:
 
 	VkDebugReportCallbackEXT debugReportCallback{};
 
+	static constexpr uint32_t kMaxTargets{3};
+	struct TargetImage {
+		VkImage image{VK_NULL_HANDLE};
+		VkDeviceMemory mem{VK_NULL_HANDLE};
+		VkSemaphore renderDone{VK_NULL_HANDLE};
+		VkSemaphore presentDone{VK_NULL_HANDLE};
+	};
+
+	TargetImage targets_[kMaxTargets];
+	uint32_t currentTarget = 0;
+
 	uint32_t getMemoryTypeIndex(uint32_t typeBits, VkMemoryPropertyFlags properties) {
 		VkPhysicalDeviceMemoryProperties deviceMemoryProperties;
 		vkGetPhysicalDeviceMemoryProperties(physicalDevice, &deviceMemoryProperties);
@@ -137,11 +150,15 @@ public:
 	/*
 		Submit command buffer to a queue and wait for fence until queue operations have been finished
 	*/
-	void submitWork(VkCommandBuffer cmdBuffer, VkQueue queue)
+	void submitWork(VkCommandBuffer cmdBuffer, VkQueue queue, VkSemaphore wait = NULL, VkSemaphore signal = NULL)
 	{
 		VkSubmitInfo submitInfo = vks::initializers::submitInfo();
 		submitInfo.commandBufferCount = 1;
 		submitInfo.pCommandBuffers = &cmdBuffer;
+		submitInfo.waitSemaphoreCount = wait == NULL ? 0 : 1;
+		submitInfo.pWaitSemaphores = wait == NULL ? NULL : &wait;
+		submitInfo.signalSemaphoreCount = signal == NULL ? 0 : 1;
+		submitInfo.pSignalSemaphores = signal == NULL ? NULL : &signal;
 		VkFenceCreateInfo fenceInfo = vks::initializers::fenceCreateInfo();
 		VkFence fence;
 		VK_CHECK_RESULT(vkCreateFence(device, &fenceInfo, nullptr, &fence));
@@ -150,7 +167,153 @@ public:
 		vkDestroyFence(device, fence, nullptr);
 	}
 
-	VulkanExample()
+	std::tuple<VkSemaphore, VkSemaphore, VkImage> render() {
+		/*
+			Command buffer creation
+		*/
+		{
+			VkCommandBuffer commandBuffer;
+			VkCommandBufferAllocateInfo cmdBufAllocateInfo =
+				vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
+			VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &commandBuffer));
+
+			VkCommandBufferBeginInfo cmdBufInfo =
+				vks::initializers::commandBufferBeginInfo();
+
+			VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &cmdBufInfo));
+
+			VkClearValue clearValues[2];
+			clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } };
+			clearValues[1].depthStencil = { 1.0f, 0 };
+
+			VkRenderPassBeginInfo renderPassBeginInfo = {};
+			renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
+			renderPassBeginInfo.renderArea.extent.width = width;
+			renderPassBeginInfo.renderArea.extent.height = height;
+			renderPassBeginInfo.clearValueCount = 2;
+			renderPassBeginInfo.pClearValues = clearValues;
+			renderPassBeginInfo.renderPass = renderPass;
+			renderPassBeginInfo.framebuffer = framebuffer;
+
+			vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
+
+			VkViewport viewport = {};
+			viewport.height = (float)height;
+			viewport.width = (float)width;
+			viewport.minDepth = (float)0.0f;
+			viewport.maxDepth = (float)1.0f;
+			vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
+
+			// Update dynamic scissor state
+			VkRect2D scissor = {};
+			scissor.extent.width = width;
+			scissor.extent.height = height;
+			vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
+
+			vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+
+			// Render scene
+			VkDeviceSize offsets[1] = { 0 };
+			vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer, offsets);
+			vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
+
+			std::vector<glm::vec3> pos = {
+				glm::vec3(-1.5f, 0.0f, -4.0f),
+				glm::vec3( 0.0f, 0.0f, -2.5f),
+				glm::vec3( 1.5f, 0.0f, -4.0f),
+			};
+
+			for (auto v : pos) {
+				glm::mat4 mvpMatrix = glm::perspective(glm::radians(60.0f), (float)width / (float)height, 0.1f, 256.0f) * glm::translate(glm::mat4(1.0f), v);
+				vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(mvpMatrix), &mvpMatrix);
+				vkCmdDrawIndexed(commandBuffer, 3, 1, 0, 0, 0);
+			}
+
+			vkCmdEndRenderPass(commandBuffer);
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
+
+			submitWork(commandBuffer, queue);
+			vkDeviceWaitIdle(device);
+		}
+
+		VkImage dstImage = targets_[currentTarget].image;
+		VkSemaphore presentDone = targets_[currentTarget].presentDone;
+		VkSemaphore renderDone = targets_[currentTarget].renderDone;
+		currentTarget = (currentTarget + 1) % kMaxTargets;
+
+		{
+			// Do the actual blit from the offscreen image to our host visible destination image
+			VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
+			VkCommandBuffer copyCmd;
+			VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &copyCmd));
+			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
+			VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
+
+			// Transition destination image to transfer destination layout
+			vks::tools::insertImageMemoryBarrier(
+				copyCmd,
+				dstImage,
+				0,
+				VK_ACCESS_TRANSFER_WRITE_BIT,
+				VK_IMAGE_LAYOUT_UNDEFINED,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+
+			// colorAttachment.image is already in VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, and does not need to be transitioned
+
+			VkImageCopy imageCopyRegion{};
+			imageCopyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageCopyRegion.srcSubresource.layerCount = 1;
+			imageCopyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			imageCopyRegion.dstSubresource.layerCount = 1;
+			imageCopyRegion.extent.width = width;
+			imageCopyRegion.extent.height = height;
+			imageCopyRegion.extent.depth = 1;
+
+			vkCmdCopyImage(
+				copyCmd,
+				colorAttachment.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
+				dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				1,
+				&imageCopyRegion);
+
+			// Transition destination image to general layout, which is the required layout for mapping the image memory later on
+			vks::tools::insertImageMemoryBarrier(
+				copyCmd,
+				dstImage,
+				VK_ACCESS_TRANSFER_WRITE_BIT,
+				VK_ACCESS_MEMORY_READ_BIT,
+				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
+				VK_IMAGE_LAYOUT_GENERAL,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VK_PIPELINE_STAGE_TRANSFER_BIT,
+				VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
+
+			VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
+
+			// Wait on presentDone and signal renderDone when finished
+			submitWork(copyCmd, queue, presentDone, renderDone);
+
+			vkFreeCommandBuffers(device, commandPool, 1, &copyCmd);
+
+			// Get layout of the image (including row pitch)
+			//VkImageSubresource subResource{};
+			//subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
+			//VkSubresourceLayout subResourceLayout;
+
+			//vkGetImageSubresourceLayout(device, dstImage, &subResource, &subResourceLayout);
+			//vkDestroyImage(device, dstImage, nullptr);
+		}
+
+		return std::make_tuple(renderDone, presentDone, dstImage);
+	}
+
+	VulkanExample(int32_t width, int32_t height)
+		: width(width)
+		, height(height)
 	{
 		LOG("Running headless rendering example\n");
 
@@ -358,13 +521,13 @@ public:
 				vkDestroyBuffer(device, stagingBuffer, nullptr);
 				vkFreeMemory(device, stagingMemory, nullptr);
 			}
+
+			vkFreeCommandBuffers(device, commandPool, 1, &copyCmd);
 		}
 
 		/*
 			Create framebuffer attachments
 		*/
-		width = 1024;
-		height = 1024;
 		VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM;
 		VkFormat depthFormat;
 		vks::tools::getSupportedDepthFormat(physicalDevice, &depthFormat);
@@ -605,81 +768,34 @@ public:
 			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
 		}
 
-		/*
-			Command buffer creation
-		*/
-		{
-			VkCommandBuffer commandBuffer;
-			VkCommandBufferAllocateInfo cmdBufAllocateInfo =
-				vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
-			VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &commandBuffer));
-
-			VkCommandBufferBeginInfo cmdBufInfo =
-				vks::initializers::commandBufferBeginInfo();
-
-			VK_CHECK_RESULT(vkBeginCommandBuffer(commandBuffer, &cmdBufInfo));
-
-			VkClearValue clearValues[2];
-			clearValues[0].color = { { 0.0f, 0.0f, 0.2f, 1.0f } };
-			clearValues[1].depthStencil = { 1.0f, 0 };
-
-			VkRenderPassBeginInfo renderPassBeginInfo = {};
-			renderPassBeginInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_BEGIN_INFO;
-			renderPassBeginInfo.renderArea.extent.width = width;
-			renderPassBeginInfo.renderArea.extent.height = height;
-			renderPassBeginInfo.clearValueCount = 2;
-			renderPassBeginInfo.pClearValues = clearValues;
-			renderPassBeginInfo.renderPass = renderPass;
-			renderPassBeginInfo.framebuffer = framebuffer;
-
-			vkCmdBeginRenderPass(commandBuffer, &renderPassBeginInfo, VK_SUBPASS_CONTENTS_INLINE);
-
-			VkViewport viewport = {};
-			viewport.height = (float)height;
-			viewport.width = (float)width;
-			viewport.minDepth = (float)0.0f;
-			viewport.maxDepth = (float)1.0f;
-			vkCmdSetViewport(commandBuffer, 0, 1, &viewport);
-
-			// Update dynamic scissor state
-			VkRect2D scissor = {};
-			scissor.extent.width = width;
-			scissor.extent.height = height;
-			vkCmdSetScissor(commandBuffer, 0, 1, &scissor);
+		vkQueueWaitIdle(queue);
 
-			vkCmdBindPipeline(commandBuffer, VK_PIPELINE_BIND_POINT_GRAPHICS, pipeline);
+		// Initialize external targets
+		for (uint32_t i = 0; i < kMaxTargets; i++) {
+			VkSemaphoreTypeCreateInfo binaryCreateInfo;
+			binaryCreateInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_TYPE_CREATE_INFO;
+			binaryCreateInfo.pNext = NULL;
+			binaryCreateInfo.semaphoreType = VK_SEMAPHORE_TYPE_BINARY;
+			binaryCreateInfo.initialValue = 0;
 
-			// Render scene
-			VkDeviceSize offsets[1] = { 0 };
-			vkCmdBindVertexBuffers(commandBuffer, 0, 1, &vertexBuffer, offsets);
-			vkCmdBindIndexBuffer(commandBuffer, indexBuffer, 0, VK_INDEX_TYPE_UINT32);
+			VkSemaphoreCreateInfo createInfo;
+			createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
+			createInfo.pNext = &binaryCreateInfo;
+			createInfo.flags = 0;
 
-			std::vector<glm::vec3> pos = {
-				glm::vec3(-1.5f, 0.0f, -4.0f),
-				glm::vec3( 0.0f, 0.0f, -2.5f),
-				glm::vec3( 1.5f, 0.0f, -4.0f),
-			};
+			VkSemaphore renderDone;
+			vkCreateSemaphore(device, &createInfo, NULL, &renderDone);
 
-			for (auto v : pos) {
-				glm::mat4 mvpMatrix = glm::perspective(glm::radians(60.0f), (float)width / (float)height, 0.1f, 256.0f) * glm::translate(glm::mat4(1.0f), v);
-				vkCmdPushConstants(commandBuffer, pipelineLayout, VK_SHADER_STAGE_VERTEX_BIT, 0, sizeof(mvpMatrix), &mvpMatrix);
-				vkCmdDrawIndexed(commandBuffer, 3, 1, 0, 0, 0);
-			}
+			VkSemaphore presentDone;
+			vkCreateSemaphore(device, &createInfo, NULL, &presentDone);
 
-			vkCmdEndRenderPass(commandBuffer);
+			// Signal Semaphore by default to avoid being stuck
+			VkSubmitInfo submitInfo;
+			submitInfo.sType = VK_STRUCTURE_TYPE_SUBMIT_INFO;
+			submitInfo.signalSemaphoreCount = 1;
+			submitInfo.pSignalSemaphores = &presentDone;
+			vkQueueSubmit(queue, 1, &submitInfo, VK_NULL_HANDLE);
 
-			VK_CHECK_RESULT(vkEndCommandBuffer(commandBuffer));
-
-			submitWork(commandBuffer, queue);
-
-			vkDeviceWaitIdle(device);
-		}
-
-		/*
-			Copy framebuffer image to host visible image
-		*/
-		const char* imagedata;
-		{
 			// Create the linear tiled destination image to copy to and to read the memory from
 			VkImageCreateInfo imgCreateInfo(vks::initializers::imageCreateInfo());
 			imgCreateInfo.imageType = VK_IMAGE_TYPE_2D;
@@ -692,135 +808,48 @@ public:
 			imgCreateInfo.initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
 			imgCreateInfo.samples = VK_SAMPLE_COUNT_1_BIT;
 			imgCreateInfo.tiling = VK_IMAGE_TILING_LINEAR;
-			imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT;
+			imgCreateInfo.usage = VK_IMAGE_USAGE_TRANSFER_DST_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT | VK_IMAGE_USAGE_SAMPLED_BIT;
+			imgCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
+
 			// Create the image
 			VkImage dstImage;
 			VK_CHECK_RESULT(vkCreateImage(device, &imgCreateInfo, nullptr, &dstImage));
+
 			// Create memory to back up the image
 			VkMemoryRequirements memRequirements;
 			VkMemoryAllocateInfo memAllocInfo(vks::initializers::memoryAllocateInfo());
 			VkDeviceMemory dstImageMemory;
 			vkGetImageMemoryRequirements(device, dstImage, &memRequirements);
 			memAllocInfo.allocationSize = memRequirements.size;
-			// Memory must be host visible to copy from
-			memAllocInfo.memoryTypeIndex = getMemoryTypeIndex(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT);
+			memAllocInfo.memoryTypeIndex = getMemoryTypeIndex(memRequirements.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
 			VK_CHECK_RESULT(vkAllocateMemory(device, &memAllocInfo, nullptr, &dstImageMemory));
 			VK_CHECK_RESULT(vkBindImageMemory(device, dstImage, dstImageMemory, 0));
 
-			// Do the actual blit from the offscreen image to our host visible destination image
-			VkCommandBufferAllocateInfo cmdBufAllocateInfo = vks::initializers::commandBufferAllocateInfo(commandPool, VK_COMMAND_BUFFER_LEVEL_PRIMARY, 1);
-			VkCommandBuffer copyCmd;
-			VK_CHECK_RESULT(vkAllocateCommandBuffers(device, &cmdBufAllocateInfo, &copyCmd));
-			VkCommandBufferBeginInfo cmdBufInfo = vks::initializers::commandBufferBeginInfo();
-			VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
-
-			// Transition destination image to transfer destination layout
-			vks::tools::insertImageMemoryBarrier(
-				copyCmd,
-				dstImage,
-				0,
-				VK_ACCESS_TRANSFER_WRITE_BIT,
-				VK_IMAGE_LAYOUT_UNDEFINED,
-				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-				VK_PIPELINE_STAGE_TRANSFER_BIT,
-				VK_PIPELINE_STAGE_TRANSFER_BIT,
-				VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
-
-			// colorAttachment.image is already in VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL, and does not need to be transitioned
-
-			VkImageCopy imageCopyRegion{};
-			imageCopyRegion.srcSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-			imageCopyRegion.srcSubresource.layerCount = 1;
-			imageCopyRegion.dstSubresource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-			imageCopyRegion.dstSubresource.layerCount = 1;
-			imageCopyRegion.extent.width = width;
-			imageCopyRegion.extent.height = height;
-			imageCopyRegion.extent.depth = 1;
-
-			vkCmdCopyImage(
-				copyCmd,
-				colorAttachment.image, VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL,
-				dstImage, VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-				1,
-				&imageCopyRegion);
-
-			// Transition destination image to general layout, which is the required layout for mapping the image memory later on
-			vks::tools::insertImageMemoryBarrier(
-				copyCmd,
-				dstImage,
-				VK_ACCESS_TRANSFER_WRITE_BIT,
-				VK_ACCESS_MEMORY_READ_BIT,
-				VK_IMAGE_LAYOUT_TRANSFER_DST_OPTIMAL,
-				VK_IMAGE_LAYOUT_GENERAL,
-				VK_PIPELINE_STAGE_TRANSFER_BIT,
-				VK_PIPELINE_STAGE_TRANSFER_BIT,
-				VkImageSubresourceRange{ VK_IMAGE_ASPECT_COLOR_BIT, 0, 1, 0, 1 });
-
-			VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
-
-			submitWork(copyCmd, queue);
-
-			// Get layout of the image (including row pitch)
-			VkImageSubresource subResource{};
-			subResource.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
-			VkSubresourceLayout subResourceLayout;
-
-			vkGetImageSubresourceLayout(device, dstImage, &subResource, &subResourceLayout);
-
-			// Map image memory so we can start copying from it
-			vkMapMemory(device, dstImageMemory, 0, VK_WHOLE_SIZE, 0, (void**)&imagedata);
-			imagedata += subResourceLayout.offset;
-
-		/*
-			Save host visible framebuffer image to disk (ppm format)
-		*/
-
-#if defined (VK_USE_PLATFORM_ANDROID_KHR)
-			const char* filename = strcat(getenv("EXTERNAL_STORAGE"), "/headless.ppm");
-#else
-			const char* filename = "headless.ppm";
-#endif
-			std::ofstream file(filename, std::ios::out | std::ios::binary);
-
-			// ppm header
-			file << "P6\n" << width << "\n" << height << "\n" << 255 << "\n";
-
-			// If source is BGR (destination is always RGB) and we can't use blit (which does automatic conversion), we'll have to manually swizzle color components
-			// Check if source is BGR and needs swizzle
-			std::vector<VkFormat> formatsBGR = { VK_FORMAT_B8G8R8A8_SRGB, VK_FORMAT_B8G8R8A8_UNORM, VK_FORMAT_B8G8R8A8_SNORM };
-			const bool colorSwizzle = (std::find(formatsBGR.begin(), formatsBGR.end(), VK_FORMAT_R8G8B8A8_UNORM) != formatsBGR.end());
-
-			// ppm binary pixel data
-			for (int32_t y = 0; y < height; y++) {
-				unsigned int *row = (unsigned int*)imagedata;
-				for (int32_t x = 0; x < width; x++) {
-					if (colorSwizzle) {
-						file.write((char*)row + 2, 1);
-						file.write((char*)row + 1, 1);
-						file.write((char*)row, 1);
-					}
-					else {
-						file.write((char*)row, 3);
-					}
-					row++;
-				}
-				imagedata += subResourceLayout.rowPitch;
-			}
-			file.close();
-
-			LOG("Framebuffer image saved to %s\n", filename);
-
-			// Clean up resources
-			vkUnmapMemory(device, dstImageMemory);
-			vkFreeMemory(device, dstImageMemory, nullptr);
-			vkDestroyImage(device, dstImage, nullptr);
+			targets_[i].image = dstImage;
+			targets_[i].mem = dstImageMemory;
+			targets_[i].presentDone = presentDone;
+			targets_[i].renderDone = renderDone;
 		}
-
-		vkQueueWaitIdle(queue);
 	}
 
 	~VulkanExample()
 	{
+		for (uint32_t i = 0; i < kMaxTargets; i++) {
+			const uint64_t waitValue = 1;
+			VkSemaphoreWaitInfo waitInfo;
+			waitInfo.pNext = NULL;
+			waitInfo.flags = 0;
+			waitInfo.semaphoreCount = 1;
+			waitInfo.pSemaphores = &targets_[i].presentDone;
+			waitInfo.pValues = &waitValue;
+			vkWaitSemaphores(device, &waitInfo, UINT64_MAX);
+
+			vkDestroyImage(device, targets_[i].image, nullptr);
+			vkFreeMemory(device, targets_[i].mem, nullptr);
+			vkDestroySemaphore(device, targets_[i].renderDone, nullptr);
+			vkDestroySemaphore(device, targets_[i].presentDone, nullptr);
+		}
+
 		vkDestroyBuffer(device, vertexBuffer, nullptr);
 		vkFreeMemory(device, vertexMemory, nullptr);
 		vkDestroyBuffer(device, indexBuffer, nullptr);
@@ -879,11 +908,19 @@ void android_main(android_app* state) {
 	}
 }
 #else
-int main() {
-	VulkanExample *vulkanExample = new VulkanExample();
-	std::cout << "Finished. Press enter to terminate...";
-	getchar();
-	delete(vulkanExample);
-	return 0;
+VulkanExample* vulkan_example_init(int32_t width, int32_t height) {
+	return new VulkanExample(width, height);
+}
+
+std::tuple<VkSemaphore, VkSemaphore, VkImage> vulkan_example_render(VulkanExample* vkExInstace) {
+	return vkExInstace->render();
+}
+
+VkDevice vulkan_example_get_device(VulkanExample* vkExInstance) {
+	return vkExInstance->device;
+}
+
+void vulkan_example_deinit(VulkanExample* vkExInstance) {
+	delete(vkExInstance);
 }
-#endif
\ No newline at end of file
+#endif
diff --git a/external/Vulkan/examples/renderheadless/renderheadless.h b/external/Vulkan/examples/renderheadless/renderheadless.h
new file mode 100644
index 0000000000000000000000000000000000000000..47202678e8ef230a42342bc4a36bbc1b5a73cddf
--- /dev/null
+++ b/external/Vulkan/examples/renderheadless/renderheadless.h
@@ -0,0 +1,16 @@
+#pragma once
+
+#include <vulkan/vulkan.h>
+
+#include <tuple>
+
+class VulkanExample;
+
+VulkanExample* vulkan_example_init(int32_t width, int32_t height);
+
+/* the first semaphore is renderDone, the second semaphore is presentDone */
+std::tuple<VkSemaphore, VkSemaphore, VkImage> vulkan_example_render(VulkanExample* vkExInstance);
+
+VkDevice vulkan_example_get_device(VulkanExample* vkExInstance);
+
+void vulkan_example_deinit(VulkanExample* vkExInstance);