Skip to content
Snippets Groups Projects
Select Git revision
  • a6cbaf15859de35350b37a6e6a4dd79308cf41a6
  • master default
2 results

renderheadless.cpp

Blame
  • renderheadless.cpp 36.08 KiB
    /*
    * Vulkan Example - Minimal headless rendering example
    *
    * Copyright (C) 2017 by Sascha Willems - www.saschawillems.de
    *
    * 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)
    #include <android/native_activity.h>
    #include <android/asset_manager.h>
    #include <android_native_app_glue.h>
    #include <android/log.h>
    #include "VulkanAndroid.h"
    #endif
    
    #include <stdio.h>
    #include <stdlib.h>
    #include <string.h>
    #include <assert.h>
    #include <vector>
    #include <array>
    #include <iostream>
    #include <algorithm>
    
    #define GLM_FORCE_RADIANS
    #define GLM_FORCE_DEPTH_ZERO_TO_ONE
    #include <glm/glm.hpp>
    #include <glm/gtc/matrix_transform.hpp>
    
    #include <vulkan/vulkan.h>
    #include "VulkanTools.h"
    #include "shaders/glsl/renderheadless/triangle.frag.spv.h"
    #include "shaders/glsl/renderheadless/triangle.vert.spv.h"
    
    #if defined(VK_USE_PLATFORM_ANDROID_KHR)
    android_app* androidapp;
    #endif
    
    #define DEBUG (!NDEBUG)
    
    #define BUFFER_ELEMENTS 32
    
    #if defined(VK_USE_PLATFORM_ANDROID_KHR)
    #define LOG(...) ((void)__android_log_print(ANDROID_LOG_INFO, "vulkanExample", __VA_ARGS__))
    #else
    #define LOG(...) printf(__VA_ARGS__)
    #endif
    
    static VKAPI_ATTR VkBool32 VKAPI_CALL debugMessageCallback(
    	VkDebugReportFlagsEXT flags,
    	VkDebugReportObjectTypeEXT objectType,
    	uint64_t object,
    	size_t location,
    	int32_t messageCode,
    	const char* pLayerPrefix,
    	const char* pMessage,
    	void* pUserData)
    {
    	LOG("[VALIDATION]: %s - %s\n", pLayerPrefix, pMessage);
    	return VK_FALSE;
    }
    
    class VulkanExample
    {
    public:
    	VkInstance instance;
    	VkPhysicalDevice physicalDevice;
    	VkDevice device;
    	uint32_t queueFamilyIndex;
    	VkPipelineCache pipelineCache;
    	VkQueue queue;
    	VkCommandPool commandPool;
    	VkCommandBuffer commandBuffer;
    	VkDescriptorSetLayout descriptorSetLayout;
    	VkPipelineLayout pipelineLayout;
    	VkPipeline pipeline;
    	std::vector<VkShaderModule> shaderModules;
    	VkBuffer vertexBuffer, indexBuffer;
    	VkDeviceMemory vertexMemory, indexMemory;
    
    	struct FrameBufferAttachment {
    		VkImage image;
    		VkDeviceMemory memory;
    		VkImageView view;
    	};
    	int32_t width, height;
    	VkFramebuffer framebuffer;
    	FrameBufferAttachment colorAttachment, depthAttachment;
    	VkRenderPass renderPass;
    
    	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);
    		for (uint32_t i = 0; i < deviceMemoryProperties.memoryTypeCount; i++) {
    			if ((typeBits & 1) == 1) {
    				if ((deviceMemoryProperties.memoryTypes[i].propertyFlags & properties) == properties) {
    					return i;
    				}
    			}
    			typeBits >>= 1;
    		}
    		return 0;
    	}
    
    	VkResult createBuffer(VkBufferUsageFlags usageFlags, VkMemoryPropertyFlags memoryPropertyFlags, VkBuffer *buffer, VkDeviceMemory *memory, VkDeviceSize size, void *data = nullptr)
    	{
    		// Create the buffer handle
    		VkBufferCreateInfo bufferCreateInfo = vks::initializers::bufferCreateInfo(usageFlags, size);
    		bufferCreateInfo.sharingMode = VK_SHARING_MODE_EXCLUSIVE;
    		VK_CHECK_RESULT(vkCreateBuffer(device, &bufferCreateInfo, nullptr, buffer));
    
    		// Create the memory backing up the buffer handle
    		VkMemoryRequirements memReqs;
    		VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
    		vkGetBufferMemoryRequirements(device, *buffer, &memReqs);
    		memAlloc.allocationSize = memReqs.size;
    		memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, memoryPropertyFlags);
    		VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, memory));
    
    		if (data != nullptr) {
    			void *mapped;
    			VK_CHECK_RESULT(vkMapMemory(device, *memory, 0, size, 0, &mapped));
    			memcpy(mapped, data, size);
    			vkUnmapMemory(device, *memory);
    		}
    
    		VK_CHECK_RESULT(vkBindBufferMemory(device, *buffer, *memory, 0));
    
    		return VK_SUCCESS;
    	}
    
    	/*
    		Submit command buffer to a queue and wait for fence until queue operations have been finished
    	*/
    	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));
    		VK_CHECK_RESULT(vkQueueSubmit(queue, 1, &submitInfo, fence));
    		VK_CHECK_RESULT(vkWaitForFences(device, 1, &fence, VK_TRUE, UINT64_MAX));
    		vkDestroyFence(device, fence, nullptr);
    	}
    
    	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");
    
    #if defined(VK_USE_PLATFORM_ANDROID_KHR)
    		LOG("loading vulkan lib");
    		vks::android::loadVulkanLibrary();
    #endif
    
    		VkApplicationInfo appInfo = {};
    		appInfo.sType = VK_STRUCTURE_TYPE_APPLICATION_INFO;
    		appInfo.pApplicationName = "Vulkan headless example";
    		appInfo.pEngineName = "VulkanExample";
    		appInfo.apiVersion = VK_API_VERSION_1_0;
    
    		/*
    			Vulkan instance creation (without surface extensions)
    		*/
    		VkInstanceCreateInfo instanceCreateInfo = {};
    		instanceCreateInfo.sType = VK_STRUCTURE_TYPE_INSTANCE_CREATE_INFO;
    		instanceCreateInfo.pApplicationInfo = &appInfo;
    
    		uint32_t layerCount = 0;
    #if defined(VK_USE_PLATFORM_ANDROID_KHR)
    		const char* validationLayers[] = { "VK_LAYER_GOOGLE_threading",	"VK_LAYER_LUNARG_parameter_validation",	"VK_LAYER_LUNARG_object_tracker","VK_LAYER_LUNARG_core_validation",	"VK_LAYER_LUNARG_swapchain", "VK_LAYER_GOOGLE_unique_objects" };
    		layerCount = 6;
    #else
    		const char* validationLayers[] = { "VK_LAYER_LUNARG_standard_validation" };
    		layerCount = 1;
    #endif
    #if DEBUG
    		// Check if layers are available
    		uint32_t instanceLayerCount;
    		vkEnumerateInstanceLayerProperties(&instanceLayerCount, nullptr);
    		std::vector<VkLayerProperties> instanceLayers(instanceLayerCount);
    		vkEnumerateInstanceLayerProperties(&instanceLayerCount, instanceLayers.data());
    
    		bool layersAvailable = true;
    		for (auto layerName : validationLayers) {
    			bool layerAvailable = false;
    			for (auto instanceLayer : instanceLayers) {
    				if (strcmp(instanceLayer.layerName, layerName) == 0) {
    					layerAvailable = true;
    					break;
    				}
    			}
    			if (!layerAvailable) {
    				layersAvailable = false;
    				break;
    			}
    		}
    
    		const char *validationExt = VK_EXT_DEBUG_REPORT_EXTENSION_NAME;
    		if (layersAvailable) {
    			instanceCreateInfo.ppEnabledLayerNames = validationLayers;
    			instanceCreateInfo.enabledLayerCount = layerCount;
    			instanceCreateInfo.enabledExtensionCount = 1;
    			instanceCreateInfo.ppEnabledExtensionNames = &validationExt;
    		}
    #endif
    		VK_CHECK_RESULT(vkCreateInstance(&instanceCreateInfo, nullptr, &instance));
    
    #if defined(VK_USE_PLATFORM_ANDROID_KHR)
    		vks::android::loadVulkanFunctions(instance);
    #endif
    #if DEBUG
    		if (layersAvailable) {
    			VkDebugReportCallbackCreateInfoEXT debugReportCreateInfo = {};
    			debugReportCreateInfo.sType = VK_STRUCTURE_TYPE_DEBUG_REPORT_CALLBACK_CREATE_INFO_EXT;
    			debugReportCreateInfo.flags = VK_DEBUG_REPORT_ERROR_BIT_EXT | VK_DEBUG_REPORT_WARNING_BIT_EXT;
    			debugReportCreateInfo.pfnCallback = (PFN_vkDebugReportCallbackEXT)debugMessageCallback;
    
    			// We have to explicitly load this function.
    			PFN_vkCreateDebugReportCallbackEXT vkCreateDebugReportCallbackEXT = reinterpret_cast<PFN_vkCreateDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkCreateDebugReportCallbackEXT"));
    			assert(vkCreateDebugReportCallbackEXT);
    			VK_CHECK_RESULT(vkCreateDebugReportCallbackEXT(instance, &debugReportCreateInfo, nullptr, &debugReportCallback));
    		}
    #endif
    
    		/*
    			Vulkan device creation
    		*/
    		uint32_t deviceCount = 0;
    		VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, nullptr));
    		std::vector<VkPhysicalDevice> physicalDevices(deviceCount);
    		VK_CHECK_RESULT(vkEnumeratePhysicalDevices(instance, &deviceCount, physicalDevices.data()));
    		physicalDevice = physicalDevices[0];
    
    		VkPhysicalDeviceProperties deviceProperties;
    		vkGetPhysicalDeviceProperties(physicalDevice, &deviceProperties);
    		LOG("GPU: %s\n", deviceProperties.deviceName);
    
    		// Request a single graphics queue
    		const float defaultQueuePriority(0.0f);
    		VkDeviceQueueCreateInfo queueCreateInfo = {};
    		uint32_t queueFamilyCount;
    		vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, nullptr);
    		std::vector<VkQueueFamilyProperties> queueFamilyProperties(queueFamilyCount);
    		vkGetPhysicalDeviceQueueFamilyProperties(physicalDevice, &queueFamilyCount, queueFamilyProperties.data());
    		for (uint32_t i = 0; i < static_cast<uint32_t>(queueFamilyProperties.size()); i++) {
    			if (queueFamilyProperties[i].queueFlags & VK_QUEUE_GRAPHICS_BIT) {
    				queueFamilyIndex = i;
    				queueCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_QUEUE_CREATE_INFO;
    				queueCreateInfo.queueFamilyIndex = i;
    				queueCreateInfo.queueCount = 1;
    				queueCreateInfo.pQueuePriorities = &defaultQueuePriority;
    				break;
    			}
    		}
    		// Create logical device
    		VkDeviceCreateInfo deviceCreateInfo = {};
    		deviceCreateInfo.sType = VK_STRUCTURE_TYPE_DEVICE_CREATE_INFO;
    		deviceCreateInfo.queueCreateInfoCount = 1;
    		deviceCreateInfo.pQueueCreateInfos = &queueCreateInfo;
    		VK_CHECK_RESULT(vkCreateDevice(physicalDevice, &deviceCreateInfo, nullptr, &device));
    
    		// Get a graphics queue
    		vkGetDeviceQueue(device, queueFamilyIndex, 0, &queue);
    
    		// Command pool
    		VkCommandPoolCreateInfo cmdPoolInfo = {};
    		cmdPoolInfo.sType = VK_STRUCTURE_TYPE_COMMAND_POOL_CREATE_INFO;
    		cmdPoolInfo.queueFamilyIndex = queueFamilyIndex;
    		cmdPoolInfo.flags = VK_COMMAND_POOL_CREATE_RESET_COMMAND_BUFFER_BIT;
    		VK_CHECK_RESULT(vkCreateCommandPool(device, &cmdPoolInfo, nullptr, &commandPool));
    
    		/*
    			Prepare vertex and index buffers
    		*/
    		struct Vertex {
    			float position[3];
    			float color[3];
    		};
    		{
    			std::vector<Vertex> vertices = {
    				{ {  1.0f,  1.0f, 0.0f }, { 1.0f, 0.0f, 0.0f } },
    				{ { -1.0f,  1.0f, 0.0f }, { 0.0f, 1.0f, 0.0f } },
    				{ {  0.0f, -1.0f, 0.0f }, { 0.0f, 0.0f, 1.0f } }
    			};
    			std::vector<uint32_t> indices = { 0, 1, 2 };
    
    			const VkDeviceSize vertexBufferSize = vertices.size() * sizeof(Vertex);
    			const VkDeviceSize indexBufferSize = indices.size() * sizeof(uint32_t);
    
    			VkBuffer stagingBuffer;
    			VkDeviceMemory stagingMemory;
    
    			// Command buffer for copy commands (reused)
    			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();
    
    			// Copy input data to VRAM using a staging buffer
    			{
    				// Vertices
    				createBuffer(
    					VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
    					VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
    					&stagingBuffer,
    					&stagingMemory,
    					vertexBufferSize,
    					vertices.data());
    
    				createBuffer(
    					VK_BUFFER_USAGE_VERTEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
    					VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
    					&vertexBuffer,
    					&vertexMemory,
    					vertexBufferSize);
    
    				VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
    				VkBufferCopy copyRegion = {};
    				copyRegion.size = vertexBufferSize;
    				vkCmdCopyBuffer(copyCmd, stagingBuffer, vertexBuffer, 1, &copyRegion);
    				VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
    
    				submitWork(copyCmd, queue);
    
    				vkDestroyBuffer(device, stagingBuffer, nullptr);
    				vkFreeMemory(device, stagingMemory, nullptr);
    
    				// Indices
    				createBuffer(
    					VK_BUFFER_USAGE_TRANSFER_SRC_BIT,
    					VK_MEMORY_PROPERTY_HOST_VISIBLE_BIT | VK_MEMORY_PROPERTY_HOST_COHERENT_BIT,
    					&stagingBuffer,
    					&stagingMemory,
    					indexBufferSize,
    					indices.data());
    
    				createBuffer(
    					VK_BUFFER_USAGE_INDEX_BUFFER_BIT | VK_BUFFER_USAGE_TRANSFER_DST_BIT,
    					VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT,
    					&indexBuffer,
    					&indexMemory,
    					indexBufferSize);
    
    				VK_CHECK_RESULT(vkBeginCommandBuffer(copyCmd, &cmdBufInfo));
    				copyRegion.size = indexBufferSize;
    				vkCmdCopyBuffer(copyCmd, stagingBuffer, indexBuffer, 1, &copyRegion);
    				VK_CHECK_RESULT(vkEndCommandBuffer(copyCmd));
    
    				submitWork(copyCmd, queue);
    
    				vkDestroyBuffer(device, stagingBuffer, nullptr);
    				vkFreeMemory(device, stagingMemory, nullptr);
    			}
    
    			vkFreeCommandBuffers(device, commandPool, 1, &copyCmd);
    		}
    
    		/*
    			Create framebuffer attachments
    		*/
    		VkFormat colorFormat = VK_FORMAT_R8G8B8A8_UNORM;
    		VkFormat depthFormat;
    		vks::tools::getSupportedDepthFormat(physicalDevice, &depthFormat);
    		{
    			// Color attachment
    			VkImageCreateInfo image = vks::initializers::imageCreateInfo();
    			image.imageType = VK_IMAGE_TYPE_2D;
    			image.format = colorFormat;
    			image.extent.width = width;
    			image.extent.height = height;
    			image.extent.depth = 1;
    			image.mipLevels = 1;
    			image.arrayLayers = 1;
    			image.samples = VK_SAMPLE_COUNT_1_BIT;
    			image.tiling = VK_IMAGE_TILING_OPTIMAL;
    			image.usage = VK_IMAGE_USAGE_COLOR_ATTACHMENT_BIT | VK_IMAGE_USAGE_TRANSFER_SRC_BIT;
    
    			VkMemoryAllocateInfo memAlloc = vks::initializers::memoryAllocateInfo();
    			VkMemoryRequirements memReqs;
    
    			VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &colorAttachment.image));
    			vkGetImageMemoryRequirements(device, colorAttachment.image, &memReqs);
    			memAlloc.allocationSize = memReqs.size;
    			memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
    			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &colorAttachment.memory));
    			VK_CHECK_RESULT(vkBindImageMemory(device, colorAttachment.image, colorAttachment.memory, 0));
    
    			VkImageViewCreateInfo colorImageView = vks::initializers::imageViewCreateInfo();
    			colorImageView.viewType = VK_IMAGE_VIEW_TYPE_2D;
    			colorImageView.format = colorFormat;
    			colorImageView.subresourceRange = {};
    			colorImageView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_COLOR_BIT;
    			colorImageView.subresourceRange.baseMipLevel = 0;
    			colorImageView.subresourceRange.levelCount = 1;
    			colorImageView.subresourceRange.baseArrayLayer = 0;
    			colorImageView.subresourceRange.layerCount = 1;
    			colorImageView.image = colorAttachment.image;
    			VK_CHECK_RESULT(vkCreateImageView(device, &colorImageView, nullptr, &colorAttachment.view));
    
    			// Depth stencil attachment
    			image.format = depthFormat;
    			image.usage = VK_IMAGE_USAGE_DEPTH_STENCIL_ATTACHMENT_BIT;
    
    			VK_CHECK_RESULT(vkCreateImage(device, &image, nullptr, &depthAttachment.image));
    			vkGetImageMemoryRequirements(device, depthAttachment.image, &memReqs);
    			memAlloc.allocationSize = memReqs.size;
    			memAlloc.memoryTypeIndex = getMemoryTypeIndex(memReqs.memoryTypeBits, VK_MEMORY_PROPERTY_DEVICE_LOCAL_BIT);
    			VK_CHECK_RESULT(vkAllocateMemory(device, &memAlloc, nullptr, &depthAttachment.memory));
    			VK_CHECK_RESULT(vkBindImageMemory(device, depthAttachment.image, depthAttachment.memory, 0));
    
    			VkImageViewCreateInfo depthStencilView = vks::initializers::imageViewCreateInfo();
    			depthStencilView.viewType = VK_IMAGE_VIEW_TYPE_2D;
    			depthStencilView.format = depthFormat;
    			depthStencilView.flags = 0;
    			depthStencilView.subresourceRange = {};
    			depthStencilView.subresourceRange.aspectMask = VK_IMAGE_ASPECT_DEPTH_BIT | VK_IMAGE_ASPECT_STENCIL_BIT;
    			depthStencilView.subresourceRange.baseMipLevel = 0;
    			depthStencilView.subresourceRange.levelCount = 1;
    			depthStencilView.subresourceRange.baseArrayLayer = 0;
    			depthStencilView.subresourceRange.layerCount = 1;
    			depthStencilView.image = depthAttachment.image;
    			VK_CHECK_RESULT(vkCreateImageView(device, &depthStencilView, nullptr, &depthAttachment.view));
    		}
    
    		/*
    			Create renderpass
    		*/
    		{
    			std::array<VkAttachmentDescription, 2> attchmentDescriptions = {};
    			// Color attachment
    			attchmentDescriptions[0].format = colorFormat;
    			attchmentDescriptions[0].samples = VK_SAMPLE_COUNT_1_BIT;
    			attchmentDescriptions[0].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    			attchmentDescriptions[0].storeOp = VK_ATTACHMENT_STORE_OP_STORE;
    			attchmentDescriptions[0].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    			attchmentDescriptions[0].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    			attchmentDescriptions[0].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    			attchmentDescriptions[0].finalLayout = VK_IMAGE_LAYOUT_TRANSFER_SRC_OPTIMAL;
    			// Depth attachment
    			attchmentDescriptions[1].format = depthFormat;
    			attchmentDescriptions[1].samples = VK_SAMPLE_COUNT_1_BIT;
    			attchmentDescriptions[1].loadOp = VK_ATTACHMENT_LOAD_OP_CLEAR;
    			attchmentDescriptions[1].storeOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    			attchmentDescriptions[1].stencilLoadOp = VK_ATTACHMENT_LOAD_OP_DONT_CARE;
    			attchmentDescriptions[1].stencilStoreOp = VK_ATTACHMENT_STORE_OP_DONT_CARE;
    			attchmentDescriptions[1].initialLayout = VK_IMAGE_LAYOUT_UNDEFINED;
    			attchmentDescriptions[1].finalLayout = VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL;
    
    			VkAttachmentReference colorReference = { 0, VK_IMAGE_LAYOUT_COLOR_ATTACHMENT_OPTIMAL };
    			VkAttachmentReference depthReference = { 1, VK_IMAGE_LAYOUT_DEPTH_STENCIL_ATTACHMENT_OPTIMAL };
    
    			VkSubpassDescription subpassDescription = {};
    			subpassDescription.pipelineBindPoint = VK_PIPELINE_BIND_POINT_GRAPHICS;
    			subpassDescription.colorAttachmentCount = 1;
    			subpassDescription.pColorAttachments = &colorReference;
    			subpassDescription.pDepthStencilAttachment = &depthReference;
    
    			// Use subpass dependencies for layout transitions
    			std::array<VkSubpassDependency, 2> dependencies;
    
    			dependencies[0].srcSubpass = VK_SUBPASS_EXTERNAL;
    			dependencies[0].dstSubpass = 0;
    			dependencies[0].srcStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    			dependencies[0].dstStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    			dependencies[0].srcAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    			dependencies[0].dstAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    			dependencies[0].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
    
    			dependencies[1].srcSubpass = 0;
    			dependencies[1].dstSubpass = VK_SUBPASS_EXTERNAL;
    			dependencies[1].srcStageMask = VK_PIPELINE_STAGE_COLOR_ATTACHMENT_OUTPUT_BIT;
    			dependencies[1].dstStageMask = VK_PIPELINE_STAGE_BOTTOM_OF_PIPE_BIT;
    			dependencies[1].srcAccessMask = VK_ACCESS_COLOR_ATTACHMENT_READ_BIT | VK_ACCESS_COLOR_ATTACHMENT_WRITE_BIT;
    			dependencies[1].dstAccessMask = VK_ACCESS_MEMORY_READ_BIT;
    			dependencies[1].dependencyFlags = VK_DEPENDENCY_BY_REGION_BIT;
    
    			// Create the actual renderpass
    			VkRenderPassCreateInfo renderPassInfo = {};
    			renderPassInfo.sType = VK_STRUCTURE_TYPE_RENDER_PASS_CREATE_INFO;
    			renderPassInfo.attachmentCount = static_cast<uint32_t>(attchmentDescriptions.size());
    			renderPassInfo.pAttachments = attchmentDescriptions.data();
    			renderPassInfo.subpassCount = 1;
    			renderPassInfo.pSubpasses = &subpassDescription;
    			renderPassInfo.dependencyCount = static_cast<uint32_t>(dependencies.size());
    			renderPassInfo.pDependencies = dependencies.data();
    			VK_CHECK_RESULT(vkCreateRenderPass(device, &renderPassInfo, nullptr, &renderPass));
    
    			VkImageView attachments[2];
    			attachments[0] = colorAttachment.view;
    			attachments[1] = depthAttachment.view;
    
    			VkFramebufferCreateInfo framebufferCreateInfo = vks::initializers::framebufferCreateInfo();
    			framebufferCreateInfo.renderPass = renderPass;
    			framebufferCreateInfo.attachmentCount = 2;
    			framebufferCreateInfo.pAttachments = attachments;
    			framebufferCreateInfo.width = width;
    			framebufferCreateInfo.height = height;
    			framebufferCreateInfo.layers = 1;
    			VK_CHECK_RESULT(vkCreateFramebuffer(device, &framebufferCreateInfo, nullptr, &framebuffer));
    		}
    
    		/*
    			Prepare graphics pipeline
    		*/
    		{
    			std::vector<VkDescriptorSetLayoutBinding> setLayoutBindings = {};
    			VkDescriptorSetLayoutCreateInfo descriptorLayout =
    				vks::initializers::descriptorSetLayoutCreateInfo(setLayoutBindings);
    			VK_CHECK_RESULT(vkCreateDescriptorSetLayout(device, &descriptorLayout, nullptr, &descriptorSetLayout));
    
    			VkPipelineLayoutCreateInfo pipelineLayoutCreateInfo =
    				vks::initializers::pipelineLayoutCreateInfo(nullptr, 0);
    
    			// MVP via push constant block
    			VkPushConstantRange pushConstantRange = vks::initializers::pushConstantRange(VK_SHADER_STAGE_VERTEX_BIT, sizeof(glm::mat4), 0);
    			pipelineLayoutCreateInfo.pushConstantRangeCount = 1;
    			pipelineLayoutCreateInfo.pPushConstantRanges = &pushConstantRange;
    
    			VK_CHECK_RESULT(vkCreatePipelineLayout(device, &pipelineLayoutCreateInfo, nullptr, &pipelineLayout));
    
    			VkPipelineCacheCreateInfo pipelineCacheCreateInfo = {};
    			pipelineCacheCreateInfo.sType = VK_STRUCTURE_TYPE_PIPELINE_CACHE_CREATE_INFO;
    			VK_CHECK_RESULT(vkCreatePipelineCache(device, &pipelineCacheCreateInfo, nullptr, &pipelineCache));
    
    			// Create pipeline
    			VkPipelineInputAssemblyStateCreateInfo inputAssemblyState =
    				vks::initializers::pipelineInputAssemblyStateCreateInfo(VK_PRIMITIVE_TOPOLOGY_TRIANGLE_LIST, 0, VK_FALSE);
    
    			VkPipelineRasterizationStateCreateInfo rasterizationState =
    				vks::initializers::pipelineRasterizationStateCreateInfo(VK_POLYGON_MODE_FILL, VK_CULL_MODE_BACK_BIT, VK_FRONT_FACE_CLOCKWISE);
    
    			VkPipelineColorBlendAttachmentState blendAttachmentState =
    				vks::initializers::pipelineColorBlendAttachmentState(0xf, VK_FALSE);
    
    			VkPipelineColorBlendStateCreateInfo colorBlendState =
    				vks::initializers::pipelineColorBlendStateCreateInfo(1, &blendAttachmentState);
    
    			VkPipelineDepthStencilStateCreateInfo depthStencilState =
    				vks::initializers::pipelineDepthStencilStateCreateInfo(VK_TRUE, VK_TRUE, VK_COMPARE_OP_LESS_OR_EQUAL);
    
    			VkPipelineViewportStateCreateInfo viewportState =
    				vks::initializers::pipelineViewportStateCreateInfo(1, 1);
    
    			VkPipelineMultisampleStateCreateInfo multisampleState =
    				vks::initializers::pipelineMultisampleStateCreateInfo(VK_SAMPLE_COUNT_1_BIT);
    
    			std::vector<VkDynamicState> dynamicStateEnables = {
    				VK_DYNAMIC_STATE_VIEWPORT,
    				VK_DYNAMIC_STATE_SCISSOR
    			};
    			VkPipelineDynamicStateCreateInfo dynamicState =
    				vks::initializers::pipelineDynamicStateCreateInfo(dynamicStateEnables);
    
    			VkGraphicsPipelineCreateInfo pipelineCreateInfo =
    				vks::initializers::pipelineCreateInfo(pipelineLayout, renderPass);
    
    			std::array<VkPipelineShaderStageCreateInfo, 2> shaderStages{};
    
    			pipelineCreateInfo.pInputAssemblyState = &inputAssemblyState;
    			pipelineCreateInfo.pRasterizationState = &rasterizationState;
    			pipelineCreateInfo.pColorBlendState = &colorBlendState;
    			pipelineCreateInfo.pMultisampleState = &multisampleState;
    			pipelineCreateInfo.pViewportState = &viewportState;
    			pipelineCreateInfo.pDepthStencilState = &depthStencilState;
    			pipelineCreateInfo.pDynamicState = &dynamicState;
    			pipelineCreateInfo.stageCount = static_cast<uint32_t>(shaderStages.size());
    			pipelineCreateInfo.pStages = shaderStages.data();
    
    			// Vertex bindings an attributes
    			// Binding description
    			std::vector<VkVertexInputBindingDescription> vertexInputBindings = {
    				vks::initializers::vertexInputBindingDescription(0, sizeof(Vertex), VK_VERTEX_INPUT_RATE_VERTEX),
    			};
    
    			// Attribute descriptions
    			std::vector<VkVertexInputAttributeDescription> vertexInputAttributes = {
    				vks::initializers::vertexInputAttributeDescription(0, 0, VK_FORMAT_R32G32B32_SFLOAT, 0),					// Position
    				vks::initializers::vertexInputAttributeDescription(0, 1, VK_FORMAT_R32G32B32_SFLOAT, sizeof(float) * 3),	// Color
    			};
    
    			VkPipelineVertexInputStateCreateInfo vertexInputState = vks::initializers::pipelineVertexInputStateCreateInfo();
    			vertexInputState.vertexBindingDescriptionCount = static_cast<uint32_t>(vertexInputBindings.size());
    			vertexInputState.pVertexBindingDescriptions = vertexInputBindings.data();
    			vertexInputState.vertexAttributeDescriptionCount = static_cast<uint32_t>(vertexInputAttributes.size());
    			vertexInputState.pVertexAttributeDescriptions = vertexInputAttributes.data();
    
    			pipelineCreateInfo.pVertexInputState = &vertexInputState;
    
    			shaderStages[0].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    			shaderStages[0].stage = VK_SHADER_STAGE_VERTEX_BIT;
    			shaderStages[0].pName = "main";
    			shaderStages[1].sType = VK_STRUCTURE_TYPE_PIPELINE_SHADER_STAGE_CREATE_INFO;
    			shaderStages[1].stage = VK_SHADER_STAGE_FRAGMENT_BIT;
    			shaderStages[1].pName = "main";
    			shaderStages[0].module = vks::tools::loadShaderCode((char *)triangle_vert_spv, triangle_vert_spv_len, device);
    			shaderStages[1].module = vks::tools::loadShaderCode((char *)triangle_frag_spv, triangle_frag_spv_len, device);
    			shaderModules = { shaderStages[0].module, shaderStages[1].module };
    			VK_CHECK_RESULT(vkCreateGraphicsPipelines(device, pipelineCache, 1, &pipelineCreateInfo, nullptr, &pipeline));
    		}
    
    		vkQueueWaitIdle(queue);
    
    		// 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;
    
    			VkSemaphoreCreateInfo createInfo;
    			createInfo.sType = VK_STRUCTURE_TYPE_SEMAPHORE_CREATE_INFO;
    			createInfo.pNext = &binaryCreateInfo;
    			createInfo.flags = 0;
    
    			VkSemaphore renderDone;
    			vkCreateSemaphore(device, &createInfo, NULL, &renderDone);
    
    			VkSemaphore presentDone;
    			vkCreateSemaphore(device, &createInfo, NULL, &presentDone);
    
    			// 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);
    
    			// 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;
    			imgCreateInfo.format = VK_FORMAT_R8G8B8A8_UNORM;
    			imgCreateInfo.extent.width = width;
    			imgCreateInfo.extent.height = height;
    			imgCreateInfo.extent.depth = 1;
    			imgCreateInfo.arrayLayers = 1;
    			imgCreateInfo.mipLevels = 1;
    			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 | 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;
    			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));
    
    			targets_[i].image = dstImage;
    			targets_[i].mem = dstImageMemory;
    			targets_[i].presentDone = presentDone;
    			targets_[i].renderDone = renderDone;
    		}
    	}
    
    	~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);
    		vkFreeMemory(device, indexMemory, nullptr);
    		vkDestroyImageView(device, colorAttachment.view, nullptr);
    		vkDestroyImage(device, colorAttachment.image, nullptr);
    		vkFreeMemory(device, colorAttachment.memory, nullptr);
    		vkDestroyImageView(device, depthAttachment.view, nullptr);
    		vkDestroyImage(device, depthAttachment.image, nullptr);
    		vkFreeMemory(device, depthAttachment.memory, nullptr);
    		vkDestroyRenderPass(device, renderPass, nullptr);
    		vkDestroyFramebuffer(device, framebuffer, nullptr);
    		vkDestroyPipelineLayout(device, pipelineLayout, nullptr);
    		vkDestroyDescriptorSetLayout(device, descriptorSetLayout, nullptr);
    		vkDestroyPipeline(device, pipeline, nullptr);
    		vkDestroyPipelineCache(device, pipelineCache, nullptr);
    		vkDestroyCommandPool(device, commandPool, nullptr);
    		for (auto shadermodule : shaderModules) {
    			vkDestroyShaderModule(device, shadermodule, nullptr);
    		}
    		vkDestroyDevice(device, nullptr);
    #if DEBUG
    		if (debugReportCallback) {
    			PFN_vkDestroyDebugReportCallbackEXT vkDestroyDebugReportCallback = reinterpret_cast<PFN_vkDestroyDebugReportCallbackEXT>(vkGetInstanceProcAddr(instance, "vkDestroyDebugReportCallbackEXT"));
    			assert(vkDestroyDebugReportCallback);
    			vkDestroyDebugReportCallback(instance, debugReportCallback, nullptr);
    		}
    #endif
    		vkDestroyInstance(instance, nullptr);
    #if defined(VK_USE_PLATFORM_ANDROID_KHR)
    		vks::android::freeVulkanLibrary();
    #endif
    	}
    };
    
    #if defined(VK_USE_PLATFORM_ANDROID_KHR)
    void handleAppCommand(android_app * app, int32_t cmd) {
    	if (cmd == APP_CMD_INIT_WINDOW) {
    		VulkanExample *vulkanExample = new VulkanExample();
    		delete(vulkanExample);
    		ANativeActivity_finish(app->activity);
    	}
    }
    void android_main(android_app* state) {
    	androidapp = state;
    	androidapp->onAppCmd = handleAppCommand;
    	int ident, events;
    	struct android_poll_source* source;
    	while ((ident = ALooper_pollAll(-1, NULL, &events, (void**)&source)) >= 0) {
    		if (source != NULL)	{
    			source->process(androidapp, source);
    		}
    		if (androidapp->destroyRequested != 0) {
    			break;
    		}
    	}
    }
    #else
    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